ownable.gno
3.13 Kb · 109 lines
1package ownable
2
3import "chain"
4
5const OwnershipTransferEvent = "OwnershipTransfer"
6
7// Ownable is meant to be used as a top-level object to make your contract
8// ownable OR being embedded in a Gno object to manage per-object ownership.
9// Ownable is safe to export as a top-level object.
10//
11// Authority-mutating methods (TransferOwnership, DropOwnership) take
12// (_ int, rlm realm). The caller threads its own cur; the method
13// asserts rlm.IsCurrent() and identifies the principal as
14// rlm.Previous().Address() — which must equal the current owner.
15//
16// o.TransferOwnership(0, cur, newOwner)
17// o.DropOwnership(0, cur)
18//
19// Read methods (OwnedBy, AssertOwnedBy) keep the bare-address shape;
20// callers extract the address themselves (e.g. cur.Previous().Address()).
21type Ownable struct {
22 owner address
23}
24
25// NewWithAddress creates an Ownable with the given address as owner.
26// This is the only constructor — the previous New/NewWithOrigin/
27// NewWithAddressByPrevious sugar baked runtime walks and an auth-mode
28// flag into the struct; the realm using this package now picks the
29// owner address explicitly (e.g. cur.Previous().Address() after
30// verifying cur.Previous().IsUserCall() in init).
31func NewWithAddress(addr address) *Ownable {
32 return &Ownable{
33 owner: addr,
34 }
35}
36
37// OwnedBy reports whether addr is the current owner.
38func (o *Ownable) OwnedBy(addr address) bool {
39 if o == nil {
40 return false
41 }
42 return addr == o.owner
43}
44
45// AssertOwnedBy panics with ErrUnauthorized if addr is not the owner.
46func (o *Ownable) AssertOwnedBy(addr address) {
47 if !o.OwnedBy(addr) {
48 panic(ErrUnauthorized)
49 }
50}
51
52// TransferOwnership transfers ownership of the Ownable to newOwner. rlm
53// must be the caller's own captured cur (asserted via rlm.IsCurrent()).
54// The principal is rlm.Previous().Address() — the realm that crossed
55// into the caller — which must equal the current owner.
56//
57// IsCurrent + rlm.Previous() makes the principal unforgeable: an
58// attacker calling TransferOwnership on a foreign Ownable cannot supply
59// an arbitrary caller address; rlm comes from a runtime-validated
60// crossing frame.
61func (o *Ownable) TransferOwnership(_ int, rlm realm, newOwner address) error {
62 if !rlm.IsCurrent() {
63 return ErrUnauthorized
64 }
65 caller := rlm.Previous().Address()
66 if !o.OwnedBy(caller) {
67 return ErrUnauthorized
68 }
69 if !newOwner.IsValid() {
70 return ErrInvalidAddress
71 }
72 prevOwner := o.owner
73 o.owner = newOwner
74 chain.Emit(
75 OwnershipTransferEvent,
76 "from", prevOwner.String(),
77 "to", newOwner.String(),
78 )
79 return nil
80}
81
82// DropOwnership removes the owner, disabling any owner-related actions.
83// rlm must be the caller's own captured cur; rlm.Previous().Address()
84// must equal the current owner.
85func (o *Ownable) DropOwnership(_ int, rlm realm) error {
86 if !rlm.IsCurrent() {
87 return ErrUnauthorized
88 }
89 caller := rlm.Previous().Address()
90 if !o.OwnedBy(caller) {
91 return ErrUnauthorized
92 }
93 prevOwner := o.owner
94 o.owner = ""
95 chain.Emit(
96 OwnershipTransferEvent,
97 "from", prevOwner.String(),
98 "to", "",
99 )
100 return nil
101}
102
103// Owner returns the owner address.
104func (o *Ownable) Owner() address {
105 if o == nil {
106 return address("")
107 }
108 return o.owner
109}