Skip to content

Commit

Permalink
feat(examples): implement r/events, p/authorizable (gnolang#2372)
Browse files Browse the repository at this point in the history
<!-- please provide a detailed description of the changes made in this
pull request. -->

## Description

This PR adds the r/gnoland/events realm. It handles events gno.land is
attending dynamically. An admin and modetators can add new events,
delete old ones, or make changes to existing ones.

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [x] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
  • Loading branch information
2 people authored and linhpn99 committed Aug 20, 2024
1 parent 5225809 commit c13905d
Show file tree
Hide file tree
Showing 18 changed files with 791 additions and 463 deletions.
4 changes: 2 additions & 2 deletions examples/gno.land/p/demo/ownable/errors.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package ownable
import "errors"

var (
ErrUnauthorized = errors.New("unauthorized; caller is not owner")
ErrInvalidAddress = errors.New("new owner address is invalid")
ErrUnauthorized = errors.New("ownable: caller is not owner")
ErrInvalidAddress = errors.New("ownable: new owner address is invalid")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Package authorizable is an extension of p/demo/ownable;
// It allows the user to instantiate an Authorizable struct, which extends
// p/demo/ownable with a list of users that are authorized for something.
// By using authorizable, you have a superuser (ownable), as well as another
// authorization level, which can be used for adding moderators or similar to your realm.
package authorizable

import (
"std"

"gno.land/p/demo/avl"
"gno.land/p/demo/ownable"
"gno.land/p/demo/ufmt"
)

type Authorizable struct {
*ownable.Ownable // owner in ownable is superuser
authorized *avl.Tree // std.Addr > struct{}{}
}

func NewAuthorizable() *Authorizable {
a := &Authorizable{
ownable.New(),
avl.NewTree(),
}

// Add owner to auth list
a.authorized.Set(a.Owner().String(), struct{}{})
return a
}

func NewAuthorizableWithAddress(addr std.Address) *Authorizable {
a := &Authorizable{
ownable.NewWithAddress(addr),
avl.NewTree(),
}

// Add owner to auth list
a.authorized.Set(a.Owner().String(), struct{}{})
return a
}

func (a *Authorizable) AddToAuthList(addr std.Address) error {
if err := a.CallerIsOwner(); err != nil {
return ErrNotSuperuser
}

if _, exists := a.authorized.Get(addr.String()); exists {
return ErrAlreadyInList
}

a.authorized.Set(addr.String(), struct{}{})

return nil
}

func (a *Authorizable) DeleteFromAuthList(addr std.Address) error {
if err := a.CallerIsOwner(); err != nil {
return ErrNotSuperuser
}

if !a.authorized.Has(addr.String()) {
return ErrNotInAuthList
}

if _, removed := a.authorized.Remove(addr.String()); !removed {
str := ufmt.Sprintf("authorizable: could not remove %s from auth list", addr.String())
panic(str)
}

return nil
}

func (a Authorizable) CallerOnAuthList() error {
caller := std.PrevRealm().Addr()

if !a.authorized.Has(caller.String()) {
return ErrNotInAuthList
}

return nil
}

func (a Authorizable) AssertOnAuthList() {
caller := std.PrevRealm().Addr()

if !a.authorized.Has(caller.String()) {
panic(ErrNotInAuthList)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package authorizable

import (
"std"
"testing"

"gno.land/p/demo/testutils"
"gno.land/p/demo/uassert"
)

var (
alice = testutils.TestAddress("alice")
bob = testutils.TestAddress("bob")
charlie = testutils.TestAddress("charlie")
)

func TestNewAuthorizable(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice) // TODO(bug, issue #2371): should not be needed

a := NewAuthorizable()
got := a.Owner()

if alice != got {
t.Fatalf("Expected %s, got: %s", alice, got)
}
}

func TestNewAuthorizableWithAddress(t *testing.T) {
a := NewAuthorizableWithAddress(alice)

got := a.Owner()

if alice != got {
t.Fatalf("Expected %s, got: %s", alice, got)
}
}

func TestCallerOnAuthList(t *testing.T) {
a := NewAuthorizableWithAddress(alice)
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

if err := a.CallerOnAuthList(); err == ErrNotInAuthList {
t.Fatalf("expected alice to be on the list")
}
}

func TestNotCallerOnAuthList(t *testing.T) {
a := NewAuthorizableWithAddress(alice)
std.TestSetRealm(std.NewUserRealm(bob))
std.TestSetOrigCaller(bob)

if err := a.CallerOnAuthList(); err == nil {
t.Fatalf("expected bob to not be on the list")
}
}

func TestAddToAuthList(t *testing.T) {
a := NewAuthorizableWithAddress(alice)
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

if err := a.AddToAuthList(bob); err != nil {
t.Fatalf("Expected no error, got %v", err)
}

std.TestSetRealm(std.NewUserRealm(bob))
std.TestSetOrigCaller(bob)

if err := a.AddToAuthList(bob); err == nil {
t.Fatalf("Expected AddToAuth to error while bob called it, but it didn't")
}
}

func TestDeleteFromList(t *testing.T) {
a := NewAuthorizableWithAddress(alice)
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

if err := a.AddToAuthList(bob); err != nil {
t.Fatalf("Expected no error, got %v", err)
}

if err := a.AddToAuthList(charlie); err != nil {
t.Fatalf("Expected no error, got %v", err)
}

std.TestSetRealm(std.NewUserRealm(bob))
std.TestSetOrigCaller(bob)

// Try an unauthorized deletion
if err := a.DeleteFromAuthList(alice); err == nil {
t.Fatalf("Expected DelFromAuth to error with %v", err)
}

std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

if err := a.DeleteFromAuthList(charlie); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
}

func TestAssertOnList(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)
a := NewAuthorizableWithAddress(alice)

std.TestSetRealm(std.NewUserRealm(bob))
std.TestSetOrigCaller(bob)

uassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() {
a.AssertOnAuthList()
})
}
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/ownable/exts/authorizable/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package authorizable

import "errors"

var (
ErrNotInAuthList = errors.New("authorizable: caller is not in authorized list")
ErrNotSuperuser = errors.New("authorizable: caller is not superuser")
ErrAlreadyInList = errors.New("authorizable: address is already in authorized list")
)
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/ownable/exts/authorizable/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module gno.land/p/demo/ownable/exts/authorizable

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/ownable v0.0.0-latest
gno.land/p/demo/testutils v0.0.0-latest
gno.land/p/demo/uassert v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
)
12 changes: 8 additions & 4 deletions examples/gno.land/p/demo/ownable/ownable.gno
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package ownable

import (
"std"
)
import "std"

const OwnershipTransferEvent = "OwnershipTransfer"

Expand All @@ -19,7 +17,9 @@ func New() *Ownable {
}

func NewWithAddress(addr std.Address) *Ownable {
return &Ownable{owner: addr}
return &Ownable{
owner: addr,
}
}

// TransferOwnership transfers ownership of the Ownable struct to a new address
Expand All @@ -40,6 +40,7 @@ func (o *Ownable) TransferOwnership(newOwner std.Address) error {
"from", string(prevOwner),
"to", string(newOwner),
)

return nil
}

Expand All @@ -64,6 +65,7 @@ func (o *Ownable) DropOwnership() error {
return nil
}

// Owner returns the owner address from Ownable
func (o Ownable) Owner() std.Address {
return o.owner
}
Expand All @@ -73,9 +75,11 @@ func (o Ownable) CallerIsOwner() error {
if std.PrevRealm().Addr() == o.owner {
return nil
}

return ErrUnauthorized
}

// AssertCallerIsOwner panics if the caller is not the owner
func (o Ownable) AssertCallerIsOwner() {
if std.PrevRealm().Addr() != o.owner {
panic(ErrUnauthorized)
Expand Down
Loading

0 comments on commit c13905d

Please sign in to comment.