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): Define Ownable and Transferrable Interfaces #2198

Draft
wants to merge 81 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
c307de3
define Ownable and Transferrable Interface for custom
linhpn99 May 26, 2024
0853491
Update the affected packages
linhpn99 May 26, 2024
a2e5c69
rename fields
linhpn99 May 26, 2024
b206494
embed interface instead of implement
linhpn99 May 26, 2024
fe82cab
update
linhpn99 May 26, 2024
e457231
update
linhpn99 May 26, 2024
d0c12d8
wrong name
linhpn99 May 26, 2024
10a767e
Merge branch 'master' into OwnableInterface
linhpn99 May 26, 2024
00f4ef0
unused error
linhpn99 May 26, 2024
1b835d2
Merge branch 'OwnableInterface' of https://github.com/linhpn99/gno in…
linhpn99 May 26, 2024
0163f8d
make tidy
linhpn99 May 26, 2024
22b8e27
make tidy again
linhpn99 May 26, 2024
deb9a29
Merge branch 'master' into OwnableInterface
linhpn99 May 26, 2024
9a34da6
Merge branch 'master' into OwnableInterface
linhpn99 May 27, 2024
3f2c0e1
Merge branch 'master' into OwnableInterface
linhpn99 May 27, 2024
c6ef120
remove helper pkg
linhpn99 May 27, 2024
cc9cf58
Merge branch 'master' into OwnableInterface
linhpn99 May 27, 2024
b04aadc
owner to transferrable
linhpn99 May 27, 2024
07ca903
Merge branch 'OwnableInterface' of https://github.com/linhpn99/gno in…
linhpn99 May 27, 2024
3b0a77c
tmp
linhpn99 May 27, 2024
8bd5ec9
tmp
linhpn99 May 27, 2024
2249f42
change to TestSetOrigCaller
linhpn99 May 27, 2024
8a8d39c
Merge branch 'master' into OwnableInterface
linhpn99 May 28, 2024
d51c88d
Merge branch 'master' into OwnableInterface
linhpn99 May 28, 2024
6764208
Merge branch 'master' into OwnableInterface
linhpn99 May 28, 2024
5d6dd97
Merge branch 'master' into OwnableInterface
linhpn99 May 29, 2024
5010520
Merge branch 'master' into OwnableInterface
linhpn99 May 29, 2024
f66a0f8
Merge branch 'master' into OwnableInterface
linhpn99 May 30, 2024
ce717f6
use TestSetRealm instead
linhpn99 May 30, 2024
c987e5e
Merge branch 'master' into OwnableInterface
linhpn99 May 31, 2024
2da9e4a
Merge branch 'master' into OwnableInterface
linhpn99 Jun 1, 2024
e162f4d
Merge branch 'master' into OwnableInterface
linhpn99 Jun 3, 2024
cceecb3
Merge branch 'master' into OwnableInterface
linhpn99 Jun 3, 2024
b71df38
Merge branch 'master' into OwnableInterface
linhpn99 Jun 6, 2024
0f36ba5
Merge branch 'master' into OwnableInterface
linhpn99 Jun 8, 2024
525449d
Merge branch 'master' into OwnableInterface
linhpn99 Jun 11, 2024
3c459fc
refactor code after manfred's review
linhpn99 Jun 11, 2024
4e51027
missing changes
linhpn99 Jun 11, 2024
6f97dfe
return interface
linhpn99 Jun 11, 2024
a6ca8bc
return interface
linhpn99 Jun 11, 2024
4765f37
update
linhpn99 Jun 11, 2024
3d19623
update
linhpn99 Jun 11, 2024
f5fe77e
rename
linhpn99 Jun 11, 2024
8bd3ac0
fixed all
linhpn99 Jun 12, 2024
a3dad4c
fixed all
linhpn99 Jun 12, 2024
0e7f9d5
fix pausable
linhpn99 Jun 12, 2024
e2aadeb
solve conflict
linhpn99 Jun 13, 2024
6fd4c2b
wrong delete
linhpn99 Jun 13, 2024
6f007f2
Merge branch 'master' into OwnableInterface
linhpn99 Jun 13, 2024
6173b88
Merge branch 'master' into OwnableInterface
linhpn99 Jun 14, 2024
0cc00c4
update from master
linhpn99 Jun 14, 2024
1dbe711
Merge branch 'master' into OwnableInterface
linhpn99 Jun 17, 2024
0fcb041
Merge branch 'master' into OwnableInterface
linhpn99 Jun 17, 2024
104140f
Merge branch 'master' into OwnableInterface
linhpn99 Jun 19, 2024
86beac8
Merge branch 'master' into OwnableInterface
linhpn99 Jun 19, 2024
97d4b8e
Merge branch 'master' into OwnableInterface
linhpn99 Jun 19, 2024
2bd3f5f
Merge branch 'master' into OwnableInterface
linhpn99 Jun 25, 2024
1fb8a5d
Merge branch 'master' into OwnableInterface
linhpn99 Jun 26, 2024
05c8f72
Merge branch 'master' into OwnableInterface
linhpn99 Jun 27, 2024
b6dec1e
Merge branch 'master' into OwnableInterface
linhpn99 Jul 3, 2024
0c2e674
Merge branch 'master' into OwnableInterface
linhpn99 Jul 3, 2024
bc3f796
Merge branch 'master' into OwnableInterface
linhpn99 Jul 4, 2024
88b7481
fix CI
linhpn99 Jul 4, 2024
daafb62
Merge branch 'master' into OwnableInterface
linhpn99 Jul 4, 2024
9fa9d29
Merge branch 'master' into OwnableInterface
linhpn99 Jul 5, 2024
15cd55e
Merge branch 'master' into OwnableInterface
linhpn99 Jul 5, 2024
9754165
Merge branch 'master' into OwnableInterface
linhpn99 Jul 6, 2024
f537530
Merge branch 'master' into OwnableInterface
linhpn99 Jul 6, 2024
d1bea90
Merge branch 'master' into OwnableInterface
linhpn99 Jul 8, 2024
efafea7
use interface instead
linhpn99 Jul 8, 2024
8ad4405
Merge branch 'master' into OwnableInterface
linhpn99 Jul 8, 2024
5326931
Merge branch 'master' into OwnableInterface
linhpn99 Jul 9, 2024
04d4b75
Merge branch 'master' into OwnableInterface
linhpn99 Jul 9, 2024
17b7b7e
Merge branch 'master' into OwnableInterface
linhpn99 Jul 14, 2024
05e9a7b
Merge branch 'master' into OwnableInterface
moul Jul 14, 2024
fd450a1
Merge branch 'master' into OwnableInterface
linhpn99 Jul 14, 2024
73f9ddb
Merge branch 'master' into OwnableInterface
linhpn99 Jul 20, 2024
9eb51d4
Merge branch 'master' into OwnableInterface
linhpn99 Jul 22, 2024
22c98f6
Merge branch 'master' into OwnableInterface
linhpn99 Aug 1, 2024
b987650
Merge branch 'master' into OwnableInterface
linhpn99 Aug 10, 2024
26d7f83
resolve conflict
linhpn99 Sep 21, 2024
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
17 changes: 11 additions & 6 deletions examples/gno.land/p/demo/memeland/memeland.gno
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ type Post struct {
}

type Memeland struct {
*ownable.Ownable
ownable.Transferrable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not exposing any transferable helper, so using the Ownable interface is sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the value returned from owner.New() is currently a transferrable, which implements the Transferrable interface. Using the Owner interface here is not appropriate

Copy link
Member

@moul moul Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you read some documentation on how Golang interfaces work.

Ownable and Transferrable are both correct. However, since you're only using the Ownable methods, I suggest receiving an Ownable rather than a Transferrable.

Copy link
Contributor Author

@linhpn99 linhpn99 Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand Go interfaces, but it seems my explanation was not correct. However, I need to confirm with you that there is a call to TransferOwnership in /r/demo/memeland. That's why i usedd Transferrable instead of Ownable

Posts []*Post
MemeCounter seqid.ID
}

func NewMemeland() *Memeland {
return &Memeland{
Ownable: ownable.New(),
Posts: make([]*Post, 0),
Transferrable: ownable.New(),
Posts: make([]*Post, 0),
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
21 changes: 16 additions & 5 deletions examples/gno.land/p/demo/memeland/memeland_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ func TestGetPostsInRangeByTimestamp(t *testing.T) {
}

func TestGetPostsInRangeByUpvote(t *testing.T) {
alice := testutils.TestAddress("alice")

m := NewMemeland()
now := time.Now()

Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand All @@ -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()

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
51 changes: 36 additions & 15 deletions examples/gno.land/p/demo/ownable/ownable.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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
moul marked this conversation as resolved.
Show resolved Hide resolved
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
}

Expand All @@ -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
}

Expand All @@ -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
}
Expand All @@ -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)
}
Expand Down
5 changes: 5 additions & 0 deletions examples/gno.land/p/demo/pausable/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pausable

import "errors"

var ErrUnauthorized = errors.New("unauthorized; address is not owner")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var ErrUnauthorized = errors.New("unauthorized; address is not owner")
var ErrUnauthorized = errors.New("unauthorized; caller is not owner")

19 changes: 16 additions & 3 deletions examples/gno.land/p/demo/pausable/pausable.gno
Original file line number Diff line number Diff line change
@@ -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
}

Expand All @@ -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,
Expand Down Expand Up @@ -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
}
20 changes: 14 additions & 6 deletions examples/gno.land/p/demo/pausable/pausable_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion examples/gno.land/r/demo/foo20/foo20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

var (
banker *grc20.Banker
admin *ownable.Ownable
admin ownable.Ownable
token grc20.Token
)

Expand Down
2 changes: 1 addition & 1 deletion examples/gno.land/r/demo/grc20factory/grc20factory.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down
Loading