ownable.gno
3.30 Kb · 127 lines
1package rbac
2
3import (
4 "chain"
5 "errors"
6)
7
8const (
9 OwnershipTransferEvent = "OwnershipTransfer"
10 OwnershipTransferStartedEvent = "OwnershipTransferStarted"
11)
12
13// Ownable2Step implements a two-step ownership transfer mechanism.
14// It requires the new owner to explicitly accept ownership before the transfer is completed,
15// preventing accidental transfers to incorrect addresses.
16//
17// Note: This package does not verify callers. Consuming realms must extract the actual
18// caller from the live realm context and pass it to these methods.
19type Ownable2Step struct {
20 owner address
21 pendingOwner address
22}
23
24// newOwnable2StepWithAddress creates a new Ownable2Step instance with addr as owner.
25func newOwnable2StepWithAddress(addr address) *Ownable2Step {
26 return &Ownable2Step{
27 owner: addr,
28 pendingOwner: "",
29 }
30}
31
32// TransferOwnershipBy initiates ownership transfer by setting newOwner as pending owner.
33// The newOwner must call AcceptOwnershipBy to complete the transfer.
34//
35// Errors:
36// - ErrUnauthorized: caller is not the current owner
37// - ErrInvalidAddress: newOwner is empty or has an invalid format
38func (o *Ownable2Step) TransferOwnershipBy(newOwner, caller address) error {
39 if !o.IsOwner(caller) {
40 return errors.New(ErrUnauthorized)
41 }
42
43 if newOwner == zeroAddress || !newOwner.IsValid() {
44 return errors.New(ErrInvalidAddress)
45 }
46
47 o.pendingOwner = newOwner
48
49 chain.Emit(
50 OwnershipTransferStartedEvent,
51 "from", o.owner.String(),
52 "to", newOwner.String(),
53 )
54
55 return nil
56}
57
58// AcceptOwnershipBy completes the ownership transfer.
59// Must be called by the pending owner.
60//
61// Errors:
62// - ErrNoPendingOwner: no ownership transfer is pending
63// - ErrPendingUnauthorized: caller is not the pending owner
64func (o *Ownable2Step) AcceptOwnershipBy(caller address) error {
65 if o.pendingOwner == zeroAddress {
66 return errors.New(ErrNoPendingOwner)
67 }
68
69 if !o.IsPendingOwner(caller) {
70 return errors.New(ErrPendingUnauthorized)
71 }
72
73 prevOwner := o.owner
74 o.owner = o.pendingOwner
75 o.pendingOwner = ""
76
77 chain.Emit(
78 OwnershipTransferEvent,
79 "from", prevOwner.String(),
80 "to", o.owner.String(),
81 )
82
83 return nil
84}
85
86// DropOwnershipBy removes the owner, disabling all owner-only actions.
87// This is irreversible - when ownership is dropped, no future owner-only operations can be performed.
88//
89// Errors:
90// - ErrUnauthorized: caller is not the current owner
91func (o *Ownable2Step) DropOwnershipBy(caller address) error {
92 if !o.IsOwner(caller) {
93 return errors.New(ErrUnauthorized)
94 }
95
96 prevOwner := o.owner
97 o.owner = ""
98 o.pendingOwner = ""
99
100 chain.Emit(
101 OwnershipTransferEvent,
102 "from", prevOwner.String(),
103 "to", "",
104 )
105
106 return nil
107}
108
109// Owner returns the current owner address. Returns empty address if ownership has been dropped.
110func (o *Ownable2Step) Owner() address {
111 return o.owner
112}
113
114// PendingOwner returns the pending owner address during ownership transfer. Returns empty address if no transfer is pending.
115func (o *Ownable2Step) PendingOwner() address {
116 return o.pendingOwner
117}
118
119// IsOwner returns true if the origin caller is the current owner.
120func (o *Ownable2Step) IsOwner(caller address) bool {
121 return o.owner == caller
122}
123
124// IsPendingOwner returns true if the origin caller is the pending owner.
125func (o *Ownable2Step) IsPendingOwner(caller address) bool {
126 return o.pendingOwner == caller
127}