diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index 48a1c15fffa..7a8eb79b69b 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -6,6 +6,8 @@ const OwnershipTransferEvent = "OwnershipTransfer" // Ownable is meant to be used as a top-level object to make your contract ownable OR // being embedded in a Gno object to manage per-object ownership. +// WARNING: Do not directly export this object at top-level, as its methods do not validate the caller. +// For this, check out SafeOwnable. type Ownable struct { owner std.Address } @@ -24,11 +26,6 @@ func NewWithAddress(addr std.Address) *Ownable { // TransferOwnership transfers ownership of the Ownable struct to a new address func (o *Ownable) TransferOwnership(newOwner std.Address) error { - err := o.CallerIsOwner() - if err != nil { - return err - } - if !newOwner.IsValid() { return ErrInvalidAddress } @@ -47,12 +44,7 @@ func (o *Ownable) TransferOwnership(newOwner std.Address) error { // DropOwnership removes the owner, effectively disabling any owner-related actions // Top-level usage: disables all only-owner actions/functions, // Embedded usage: behaves like a burn functionality, removing the owner from the struct -func (o *Ownable) DropOwnership() error { - err := o.CallerIsOwner() - if err != nil { - return err - } - +func (o *Ownable) DropOwnership() { prevOwner := o.owner o.owner = "" @@ -61,8 +53,6 @@ func (o *Ownable) DropOwnership() error { "from", prevOwner.String(), "to", "", ) - - return nil } // Owner returns the owner address from Ownable @@ -71,12 +61,8 @@ func (o Ownable) Owner() std.Address { } // CallerIsOwner checks if the caller of the function is the Realm's owner -func (o Ownable) CallerIsOwner() error { - if std.PrevRealm().Addr() == o.owner { - return nil - } - - return ErrUnauthorized +func (o Ownable) CallerIsOwner() bool { + return std.PrevRealm().Addr() == o.owner } // AssertCallerIsOwner panics if the caller is not the owner @@ -85,3 +71,45 @@ func (o Ownable) AssertCallerIsOwner() { panic(ErrUnauthorized) } } + +// Safe-object + +// SafeOwnable is an exportable struct that wraps Ownable +// By exposing a SafeOwnable object, a realm will expose all SafeOwnable exported methods +// to the public automatically. These methods, as per the implementation, +// are only callable by the Ownable's admin. +type SafeOwnable struct { + ownable *Ownable +} + +func (o *Ownable) NewSafeOwnable() *SafeOwnable { + return &SafeOwnable{ + ownable: o, + } +} + +// AssertCallerIsOwner panics if the caller is not the owner +func (so SafeOwnable) AssertCallerIsOwner() { + so.ownable.AssertCallerIsOwner() +} + +func (so *SafeOwnable) TransferOwnership(newOwner std.Address) error { + if !so.ownable.CallerIsOwner() { + return ErrUnauthorized + } + + if err := so.ownable.TransferOwnership(newOwner); err != nil { + return err + } + + return nil +} + +func (so *SafeOwnable) DropOwnership() error { + if !so.ownable.CallerIsOwner() { + return ErrUnauthorized + } + + so.ownable.DropOwnership() + return nil +} diff --git a/examples/gno.land/p/demo/ownable/ownable_test.gno b/examples/gno.land/p/demo/ownable/ownable_test.gno index dee40fa6e1d..16a8550f459 100644 --- a/examples/gno.land/p/demo/ownable/ownable_test.gno +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -1,6 +1,7 @@ package ownable import ( + "github.com/gnolang/gno/examples/gno.land/p/demo/urequire" "std" "testing" @@ -13,24 +14,23 @@ var ( bob = testutils.TestAddress("bob") ) +// embed ownable and test +// test safeownable + func TestNew(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) std.TestSetOrigCaller(alice) // TODO(bug): should not be needed o := New() got := o.Owner() - if alice != got { - t.Fatalf("Expected %s, got: %s", alice, got) - } + uassert.Equal(t, got, alice) } func TestNewWithAddress(t *testing.T) { o := NewWithAddress(alice) got := o.Owner() - if alice != got { - t.Fatalf("Expected %s, got: %s", alice, got) - } + uassert.Equal(t, got, alice) } func TestTransferOwnership(t *testing.T) { @@ -39,14 +39,9 @@ func TestTransferOwnership(t *testing.T) { o := New() err := o.TransferOwnership(bob) - if err != nil { - t.Fatalf("TransferOwnership failed, %v", err) - } got := o.Owner() - if bob != got { - t.Fatalf("Expected: %s, got: %s", bob, got) - } + uassert.Equal(t, got, bob) } func TestCallerIsOwner(t *testing.T) { @@ -58,8 +53,7 @@ func TestCallerIsOwner(t *testing.T) { std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) std.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed - err := o.CallerIsOwner() - uassert.Error(t, err) // XXX: IsError(..., unauthorizedCaller) + uassert.False(t, o.CallerIsOwner()) } func TestDropOwnership(t *testing.T) { @@ -68,7 +62,7 @@ func TestDropOwnership(t *testing.T) { o := New() err := o.DropOwnership() - uassert.NoError(t, err, "DropOwnership failed") + urequire.NoError(t, err, "DropOwnership failed") owner := o.Owner() uassert.Empty(t, owner, "owner should be empty") @@ -85,13 +79,8 @@ func TestErrUnauthorized(t *testing.T) { std.TestSetRealm(std.NewUserRealm(bob)) std.TestSetOrigCaller(bob) // TODO(bug): should not be needed - err := o.TransferOwnership(alice) - if err != ErrUnauthorized { - t.Fatalf("Should've been ErrUnauthorized, was %v", err) - } - - err = o.DropOwnership() - uassert.ErrorContains(t, err, ErrUnauthorized.Error()) + uassert.ErrorContains(t, o.TransferOwnership(alice), ErrUnauthorized.Error()) + uassert.ErrorContains(t, o.DropOwnership(), ErrUnauthorized.Error()) } func TestErrInvalidAddress(t *testing.T) { diff --git a/examples/gno.land/p/demo/ownable/z0_filetest.gno b/examples/gno.land/p/demo/ownable/z0_filetest.gno new file mode 100644 index 00000000000..f94fc50e589 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/z0_filetest.gno @@ -0,0 +1,8 @@ +package main + +import "gno.land/p/demo/ownable" + +func main() { + _ = ownable.New() + +}