Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): ownable & pausable safe objects #3331

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 47 additions & 19 deletions examples/gno.land/p/demo/ownable/ownable.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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 = ""

Expand All @@ -61,8 +53,6 @@ func (o *Ownable) DropOwnership() error {
"from", prevOwner.String(),
"to", "",
)

return nil
}

// Owner returns the owner address from Ownable
Expand All @@ -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
Expand All @@ -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
}
33 changes: 11 additions & 22 deletions examples/gno.land/p/demo/ownable/ownable_test.gno
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ownable

import (
"github.com/gnolang/gno/examples/gno.land/p/demo/urequire"
"std"
"testing"

Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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")
Expand All @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions examples/gno.land/p/demo/ownable/z0_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

import "gno.land/p/demo/ownable"

func main() {
_ = ownable.New()

}
Loading