diff --git a/examples/gno.land/p/demo/memeland/memeland.gno b/examples/gno.land/p/demo/memeland/memeland.gno index 9c302ca365b..efa50c0bc3a 100644 --- a/examples/gno.land/p/demo/memeland/memeland.gno +++ b/examples/gno.land/p/demo/memeland/memeland.gno @@ -26,15 +26,15 @@ type Post struct { } type Memeland struct { - *ownable.Ownable + ownable.Transferrable Posts []*Post MemeCounter seqid.ID } func NewMemeland() *Memeland { return &Memeland{ - Ownable: ownable.New(), - Posts: make([]*Post, 0), + Transferrable: ownable.New(), + Posts: make([]*Post, 0), } } @@ -160,9 +160,7 @@ func (m *Memeland) RemovePost(id string) string { panic("id cannot be empty") } - if err := m.CallerIsOwner(); err != nil { - panic(err) - } + m.AssertCallerIsOwner() for i, post := range m.Posts { if post.ID == id { @@ -174,6 +172,13 @@ func (m *Memeland) RemovePost(id string) string { panic("post with specified id does not exist") } +// AssertCallerIsOwner panics if the caller is not the Realm's owner +func (m *Memeland) AssertCallerIsOwner() { + if std.PrevRealm().Addr() != m.Owner() { + panic("unauthorized; caller is not owner") + } +} + // PostsToJSONString converts a slice of Post structs into a JSON string func PostsToJSONString(posts []*Post) string { var sb strings.Builder diff --git a/examples/gno.land/p/demo/memeland/memeland_test.gno b/examples/gno.land/p/demo/memeland/memeland_test.gno index 95065b8cd64..2353f24f97f 100644 --- a/examples/gno.land/p/demo/memeland/memeland_test.gno +++ b/examples/gno.land/p/demo/memeland/memeland_test.gno @@ -108,6 +108,8 @@ func TestGetPostsInRangeByTimestamp(t *testing.T) { } func TestGetPostsInRangeByUpvote(t *testing.T) { + alice := testutils.TestAddress("alice") + m := NewMemeland() now := time.Now() @@ -122,7 +124,8 @@ func TestGetPostsInRangeByUpvote(t *testing.T) { m.Upvote(id2) // Change caller so avoid double upvote panic - std.TestSetOrigCaller(testutils.TestAddress("alice")) + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // TODO(bug): should not be needed m.Upvote(id1) // Final upvote count: @@ -236,14 +239,18 @@ func TestUpvote(t *testing.T) { func TestDelete(t *testing.T) { alice := testutils.TestAddress("alice") - std.TestSetOrigCaller(alice) + + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // TODO(bug): should not be needed // Alice is admin m := NewMemeland() // Set caller to Bob bob := testutils.TestAddress("bob") - std.TestSetOrigCaller(bob) + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) // TODO(bug): should not be needed // Bob adds post to Memeland now := time.Now() @@ -259,7 +266,9 @@ func TestDelete(t *testing.T) { func TestDeleteByNonAdmin(t *testing.T) { alice := testutils.TestAddress("alice") - std.TestSetOrigCaller(alice) + + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // TODO(bug): should not be needed m := NewMemeland() @@ -269,7 +278,9 @@ func TestDeleteByNonAdmin(t *testing.T) { // Bob will try to delete meme posted by Alice, which should fail bob := testutils.TestAddress("bob") - std.TestSetOrigCaller(bob) + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOrigCaller(bob) // TODO(bug): should not be needed defer func() { if r := recover(); r == nil { diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno index f9f0ea15dd9..9c5b5e9b61e 100644 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno @@ -14,8 +14,8 @@ import ( ) type Authorizable struct { - *ownable.Ownable // owner in ownable is superuser - authorized *avl.Tree // std.Addr > struct{}{} + ownable.Ownable // owner in ownable is superuser + authorized *avl.Tree // std.Addr > struct{}{} } func NewAuthorizable() *Authorizable { diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index a77b22461a9..c995e136e96 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -6,26 +6,48 @@ 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. -type Ownable struct { +type Ownable interface { + Owner() std.Address + CallerIsOwner() error + AssertCallerIsOwner() +} + +// Transferrable embeds Ownable, indicating that the owner can transfer ownership and drop ownership +type Transferrable interface { + Ownable + + TransferOwnership(newOwner std.Address) error + DropOwnership() error +} + +// transferrable provides a basic implementation of ownership functionality +// It can be used as a top-level object to make a contract ownable or embedded within +// another object to manage per-object ownership. +type transferrable struct { owner std.Address } -func New() *Ownable { - return &Ownable{ +// Ensure that transferrable implements the Ownable and Transferrable interfaces +var ( + _ Ownable = (*transferrable)(nil) + _ Transferrable = (*transferrable)(nil) +) + +func New() *transferrable { + return &transferrable{ owner: std.PrevRealm().Addr(), } } -func NewWithAddress(addr std.Address) *Ownable { - return &Ownable{ +func NewWithAddress(addr std.Address) *transferrable { + return &transferrable{ owner: addr, } } // 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 { +func (o *transferrable) TransferOwnership(newOwner std.Address) error { + if err := o.CallerIsOwner(); err != nil { return err } @@ -47,9 +69,8 @@ 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 { +func (o *transferrable) DropOwnership() error { + if err := o.CallerIsOwner(); err != nil { return err } @@ -65,13 +86,13 @@ func (o *Ownable) DropOwnership() error { return nil } -// Owner returns the owner address from Ownable -func (o Ownable) Owner() std.Address { +// Owner returns the owner address from transferrable +func (o transferrable) Owner() std.Address { return o.owner } // CallerIsOwner checks if the caller of the function is the Realm's owner -func (o Ownable) CallerIsOwner() error { +func (o transferrable) CallerIsOwner() error { if std.PrevRealm().Addr() == o.owner { return nil } @@ -80,7 +101,7 @@ func (o Ownable) CallerIsOwner() error { } // AssertCallerIsOwner panics if the caller is not the owner -func (o Ownable) AssertCallerIsOwner() { +func (o transferrable) AssertCallerIsOwner() { if std.PrevRealm().Addr() != o.owner { panic(ErrUnauthorized) } diff --git a/examples/gno.land/p/demo/pausable/errors.gno b/examples/gno.land/p/demo/pausable/errors.gno new file mode 100644 index 00000000000..bcb7a6875d8 --- /dev/null +++ b/examples/gno.land/p/demo/pausable/errors.gno @@ -0,0 +1,5 @@ +package pausable + +import "errors" + +var ErrUnauthorized = errors.New("unauthorized; address is not owner") diff --git a/examples/gno.land/p/demo/pausable/pausable.gno b/examples/gno.land/p/demo/pausable/pausable.gno index eae3456ba61..55105ad7559 100644 --- a/examples/gno.land/p/demo/pausable/pausable.gno +++ b/examples/gno.land/p/demo/pausable/pausable.gno @@ -1,9 +1,13 @@ package pausable -import "gno.land/p/demo/ownable" +import ( + "std" + + "gno.land/p/demo/ownable" +) type Pausable struct { - *ownable.Ownable + ownable.Ownable paused bool } @@ -16,7 +20,7 @@ func New() *Pausable { } // NewFromOwnable is the same as New, but with a pre-existing top-level ownable -func NewFromOwnable(ownable *ownable.Ownable) *Pausable { +func NewFromOwnable(ownable ownable.Ownable) *Pausable { return &Pausable{ Ownable: ownable, paused: false, @@ -47,3 +51,12 @@ func (p *Pausable) Unpause() error { p.paused = false return nil } + +// CallerIsOwner checks if the caller of the function is the Realm's owner +func (p Pausable) CallerIsOwner() error { + if std.PrevRealm().Addr() == p.Owner() { + return nil + } + + return ErrUnauthorized +} diff --git a/examples/gno.land/p/demo/pausable/pausable_test.gno b/examples/gno.land/p/demo/pausable/pausable_test.gno index c9557245bdf..a5f53d87109 100644 --- a/examples/gno.land/p/demo/pausable/pausable_test.gno +++ b/examples/gno.land/p/demo/pausable/pausable_test.gno @@ -14,7 +14,8 @@ var ( ) func TestNew(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed result := New() @@ -23,17 +24,22 @@ func TestNew(t *testing.T) { } func TestNewFromOwnable(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed + o := ownable.New() - std.TestSetOrigCaller(secondCaller) + std.TestSetRealm(std.NewUserRealm(secondCaller)) + std.TestSetOrigCaller(secondCaller) // TODO(bug): should not be needed + result := NewFromOwnable(o) urequire.Equal(t, firstCaller.String(), result.Owner().String()) } func TestSetUnpaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed result := New() result.Unpause() @@ -42,7 +48,8 @@ func TestSetUnpaused(t *testing.T) { } func TestSetPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed result := New() result.Pause() @@ -51,7 +58,8 @@ func TestSetPaused(t *testing.T) { } func TestIsPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetRealm(std.NewUserRealm(firstCaller)) + std.TestSetOrigCaller(firstCaller) // TODO(bug): should not be needed result := New() urequire.False(t, result.IsPaused(), "Expected result to be unpaused") diff --git a/examples/gno.land/r/demo/foo20/foo20.gno b/examples/gno.land/r/demo/foo20/foo20.gno index 9d4e5d40193..bc237eb33f3 100644 --- a/examples/gno.land/r/demo/foo20/foo20.gno +++ b/examples/gno.land/r/demo/foo20/foo20.gno @@ -15,7 +15,7 @@ import ( var ( banker *grc20.Banker - admin *ownable.Ownable + admin ownable.Ownable token grc20.Token ) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory.gno b/examples/gno.land/r/demo/grc20factory/grc20factory.gno index f37a9370a9e..6f19bafc28b 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory.gno @@ -39,7 +39,7 @@ func NewWithAdmin(name, symbol string, decimals uint, initialMint, faucet uint64 type instance struct { banker *grc20.Banker - admin *ownable.Ownable + admin ownable.Ownable faucet uint64 // per-request amount. disabled if 0. }