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): hall of fame #2842

Open
wants to merge 74 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ac0d79e
feat: add p/avl/pager
moul Jul 14, 2024
a17edb2
chore: fixup
moul Jul 14, 2024
1a44f35
chore: fixup
moul Jul 14, 2024
291b92f
chore: fixup
moul Jul 14, 2024
6038562
chore: fixup
moul Jul 14, 2024
23db6aa
chore: fixup
moul Jul 14, 2024
fa7286b
chore: fixup
moul Jul 14, 2024
c0d435d
chore: fixup
moul Jul 14, 2024
1cff9f1
chore: fixup
moul Jul 14, 2024
04d62c7
Merge remote-tracking branch 'origin/master' into dev/moul/pagination
moul Jul 14, 2024
b2294ac
chore: fixup
moul Jul 14, 2024
2a1093e
chore: fixup
moul Jul 14, 2024
903fb48
Update pager_test.gno
moul Jul 14, 2024
f1adda0
chore: fixup
moul Jul 14, 2024
5d8a12b
add initial hof
leohhhn Sep 24, 2024
538f6f1
add
leohhhn Sep 24, 2024
7d44835
Merge branch 'master' into dev/moul/pagination
moul Sep 24, 2024
c8e4e1f
chore: fixup
moul Sep 24, 2024
a0be035
chore: fixup
moul Sep 24, 2024
5c3524d
chore: fixup
moul Sep 24, 2024
17b7ba6
add item & exh rendering
leohhhn Sep 25, 2024
29bb8dc
save
leohhhn Sep 25, 2024
5393e7b
update rendering, add upvote/downvote
leohhhn Sep 25, 2024
9a171ff
register temp
leohhhn Sep 26, 2024
97794d7
Merge remote-tracking branch 'origin/master' into dev/moul/pagination
moul Sep 26, 2024
1da8805
chore: new API
moul Sep 26, 2024
e2dcbca
Merge branch 'master' into hof
leohhhn Sep 30, 2024
ec2687b
add cmts
leohhhn Sep 30, 2024
3c21976
cmts
leohhhn Sep 30, 2024
fa11b61
start cs funcs
leohhhn Sep 30, 2024
48ebe1a
rm cmts
leohhhn Oct 1, 2024
5a5c1b5
add widget
leohhhn Oct 1, 2024
2cef5df
add temp exhib
leohhhn Oct 1, 2024
54e414b
rendering
leohhhn Oct 1, 2024
8f3d796
rendering
leohhhn Oct 1, 2024
4a6dad6
rm blog register
leohhhn Oct 1, 2024
5dc3d0f
save
leohhhn Oct 1, 2024
f0ce5de
save
leohhhn Oct 1, 2024
943df0c
Merge branch 'master' into dev/moul/pagination
moul Oct 3, 2024
296f7d8
rm errs
leohhhn Oct 4, 2024
1c10732
Update examples/gno.land/p/demo/avl/pager/pager.gno
moul Oct 5, 2024
79ffd7d
chore: fixup
moul Oct 5, 2024
adf95c0
Merge branch 'master' into dev/moul/pagination
moul Oct 5, 2024
3754a70
chore: fixup
moul Oct 5, 2024
19f7fa9
rm temp exhib
leohhhn Oct 7, 2024
a646518
add pausable
leohhhn Oct 7, 2024
94fc361
expose apis, add pausable
leohhhn Oct 7, 2024
cde9fe2
fix errors
leohhhn Oct 7, 2024
84ef0ea
update ownable/pausable, home, leon/home
leohhhn Oct 7, 2024
0c9b8a6
move
leohhhn Oct 7, 2024
d1d60ed
add shortlink
leohhhn Oct 7, 2024
f6d07a9
add shortpath, fix
leohhhn Oct 7, 2024
dd528a5
newline
leohhhn Oct 7, 2024
57fbda6
add tests
leohhhn Oct 7, 2024
e14f677
add sort avltree
leohhhn Oct 7, 2024
d3de1e1
add tests
leohhhn Oct 7, 2024
04061a8
add tests
leohhhn Oct 7, 2024
c3ea204
add godoc
leohhhn Oct 7, 2024
20b6d9a
Merge branch 'master' into hof
leohhhn Oct 8, 2024
f32070f
ownable
leohhhn Oct 8, 2024
1cecdd5
Merge remote-tracking branch 'moulfork/dev/moul/pagination' into hof
leohhhn Oct 8, 2024
822ae4b
register to hof, add pagination
leohhhn Oct 8, 2024
5b7afc8
renaming
leohhhn Oct 8, 2024
b40e128
add codeowner
leohhhn Oct 8, 2024
8a55c2a
add codeowner
leohhhn Oct 8, 2024
edc6915
rm hof
leohhhn Oct 8, 2024
f31c2e9
fix tests, fmt
leohhhn Oct 8, 2024
7602379
format import
leohhhn Oct 8, 2024
6e0f3c5
Merge branch 'master' into hof
leohhhn Oct 15, 2024
8bcf672
rm panics, just return
leohhhn Oct 15, 2024
3f35e68
fix tests
leohhhn Oct 15, 2024
2f88736
mod tidy
leohhhn Oct 15, 2024
0352dfc
Merge branch 'master' into hof
leohhhn Oct 16, 2024
1cc0a9e
update codeowners
leohhhn Oct 16, 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
20 changes: 19 additions & 1 deletion examples/gno.land/p/demo/fqname/fqname.gno
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
// package-level declaration.
package fqname

import "strings"
import (
"strings"
)

// Parse splits a fully qualified identifier into its package path and name
// components. It handles cases with and without slashes in the package path.
Expand Down Expand Up @@ -63,10 +65,26 @@ func RenderLink(pkgPath, slug string) string {
if slug != "" {
return "[" + pkgPath + "](" + pkgLink + ")." + slug
}

return "[" + pkgPath + "](" + pkgLink + ")"
}

if slug != "" {
return pkgPath + "." + slug
}

return pkgPath
}

// ShortPath returns the part of the pkgpath after the base domain.
// sp := fqname.ShortPath("gno.land/r/gnoland/home")
// fmt.Println(sp)
// // Output: /r/gnoland/home
func ShortPath(pkgPath string) string {
idx := strings.Index(pkgPath, "/")
if idx == -1 {
return ""
}

return pkgPath[idx:]
}
19 changes: 19 additions & 0 deletions examples/gno.land/p/demo/fqname/fqname_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,22 @@ func TestRenderLink(t *testing.T) {
uassert.Equal(t, tt.expected, result, "Rendered link did not match expected")
}
}

func TestShortPath(t *testing.T) {
tests := []struct {
pkgPath string
expected string
}{
{"gno.land/p/demo/avl", "/p/demo/avl"},
{"github.com/a/b", "/a/b"},
{"example.com/pkg", "/pkg"},
{"gno.land/r/demo/foo20", "/r/demo/foo20"},
{"gno.land/r/demo/foo20", "/r/demo/foo20"},
{"", ""},
}

for _, tt := range tests {
result := ShortPath(tt.pkgPath)
uassert.Equal(t, tt.expected, result, "Rendered link did not match expected")
}
}
2 changes: 1 addition & 1 deletion examples/gno.land/p/demo/ownable/ownable.gno
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (o *Ownable) DropOwnership() error {

std.Emit(
OwnershipTransferEvent,
"from", string(prevOwner),
"from", prevOwner.String(),
leohhhn marked this conversation as resolved.
Show resolved Hide resolved
"to", "",
)

Expand Down
10 changes: 9 additions & 1 deletion examples/gno.land/p/demo/pausable/pausable.gno
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package pausable

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

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

type Pausable struct {
*ownable.Ownable
Expand Down Expand Up @@ -35,6 +39,8 @@ func (p *Pausable) Pause() error {
}

p.paused = true
std.Emit("Paused", "account", p.Owner().String())

return nil
}

Expand All @@ -45,5 +51,7 @@ func (p *Pausable) Unpause() error {
}

p.paused = false
std.Emit("Unpaused", "account", p.Owner().String())

return nil
}
24 changes: 24 additions & 0 deletions examples/gno.land/r/demo/hof/administration.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package hof

import "std"

// Exposing the ownable & pausable APIs
// Should not be needed as soon as MsgCall supports calling methods on exported variables

func Pause() error {
return exhibition.Pause()
}

func Unpause() error {
return exhibition.Unpause()
}

func GetOwner() std.Address {
return owner.Owner()
}

func TransferOwnership(newOwner std.Address) {
if err := owner.TransferOwnership(newOwner); err != nil {
panic(err)
}
}
14 changes: 14 additions & 0 deletions examples/gno.land/r/demo/hof/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package hof

import (
"errors"
)

var (
ErrNoSuchItem = errors.New("hof: no such item exists")
ErrAlreadyExists = errors.New("hof: alerady exists")
ErrDoubleUpvote = errors.New("hof: cannot upvote twice")
ErrDoubleDownvote = errors.New("hof: cannot downvote twice")
ErrNonCodeCall = errors.New("hof: non-code call")
ErrPaused = errors.New("hof: registering is paused")
)
10 changes: 10 additions & 0 deletions examples/gno.land/r/demo/hof/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module gno.land/r/demo/hof

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/fqname v0.0.0-latest
gno.land/p/demo/ownable v0.0.0-latest
gno.land/p/demo/pausable v0.0.0-latest
gno.land/p/demo/seqid v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
)
131 changes: 131 additions & 0 deletions examples/gno.land/r/demo/hof/hof.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Package hof is the hall of fame realm.
// The Hall of Fame is an exhibition that holds items. Users can add their realms to the Hall of Fame by
// importing the Hall of Fame realm and calling hof.Register() from their init function.
package hof

import (
"std"

"gno.land/p/demo/avl"
"gno.land/p/demo/ownable"
"gno.land/p/demo/pausable"
"gno.land/p/demo/seqid"
)

var (
exhibition *Exhibition
owner *ownable.Ownable
)

type (
Exhibition struct {
itemCounter seqid.ID
description string
items *avl.Tree // pkgPath > Item
itemsSorted *avl.Tree // same data but sorted, storing pointers
*pausable.Pausable
}

Item struct {
id seqid.ID
pkgpath string
deployer std.Address
blockNum int64
upvote *avl.Tree // std.Addr > struct{}{}
downvote *avl.Tree // std.Addr > struct{}{}
}
)

func init() {
exhibition = &Exhibition{
items: avl.NewTree(),
itemsSorted: avl.NewTree(),
}

owner = ownable.NewWithAddress(std.Address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"))
exhibition.Pausable = pausable.NewFromOwnable(owner)
}

// Register registers your realm to the Hall of Fame
// Should be called from within code
func Register() {
if exhibition.IsPaused() {
panic(ErrPaused.Error())
}

submission := std.PrevRealm()
pkgpath := submission.PkgPath()

// Must not yet exist and must be called from code
if submission.IsUser() {
panic(ErrNonCodeCall.Error())
Copy link
Member

Choose a reason for hiding this comment

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

I think we should not panic about registration but instead return an error.

If someone has a conditional registration that can occur 0 or N times, it will brick their contract.

Additionally, when we add "upgradeable contracts," the init() function can be called multiple times.

I suggest returning an error but ignoring it from the caller by default, as we don't need to address it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#2842 (comment)

What about this? :D

Copy link
Member

Choose a reason for hiding this comment

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

I'm sorry if I initially said something wrong; I don't recall the context.

However, Register should not panic. Let's avoid returning anything and don't panic.

The goal is to ensure you're registered at launch without needing complex logic to check if you were already registered. It's similar to a "Set" or "Upsert" in other systems (can override with the same value), or sending a "wakeup" UDP packet where it doesn't matter if the system is already up and running.

Copy link
Contributor Author

@leohhhn leohhhn Oct 15, 2024

Choose a reason for hiding this comment

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

}

if exhibition.items.Has(pkgpath) {
panic(ErrAlreadyExists.Error())
}

id := exhibition.itemCounter.Next()
i := &Item{
id: id,
pkgpath: pkgpath,
deployer: std.GetOrigCaller(),
blockNum: std.GetHeight(),
upvote: avl.NewTree(),
downvote: avl.NewTree(),
}

exhibition.items.Set(pkgpath, i)
exhibition.itemsSorted.Set(id.String(), i)
}

func Upvote(pkgpath string) {
rawItem, ok := exhibition.items.Get(pkgpath)
if !ok {
panic(ErrNoSuchItem.Error())
}

item := rawItem.(*Item)
caller := std.PrevRealm().Addr().String()

if item.upvote.Has(caller) {
panic(ErrDoubleUpvote.Error())
}

item.upvote.Set(caller, struct{}{})
}

func Downvote(pkgpath string) {
rawItem, ok := exhibition.items.Get(pkgpath)
if !ok {
panic(ErrNoSuchItem.Error())
}

item := rawItem.(*Item)
caller := std.PrevRealm().Addr().String()

if item.downvote.Has(caller) {
panic(ErrDoubleDownvote.Error())
}

item.downvote.Set(caller, struct{}{})
}

func Delete(pkgpath string) {
if err := owner.CallerIsOwner(); err != nil {
panic(err)
}

i, ok := exhibition.items.Get(pkgpath)
if !ok {
panic(ErrNoSuchItem.Error())
}

if _, removed := exhibition.itemsSorted.Remove(i.(*Item).id.String()); !removed {
panic(ErrNoSuchItem.Error())
}

if _, removed := exhibition.items.Remove(pkgpath); !removed {
panic(ErrNoSuchItem.Error())
}
}
110 changes: 110 additions & 0 deletions examples/gno.land/r/demo/hof/hof_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package hof

import (
"std"
"testing"

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

const rlm1Path = "gno.land/r/leon/home"

var admin = std.Address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")

func TestRegister(t *testing.T) {
userRealm := std.NewUserRealm(admin)
std.TestSetRealm(userRealm)
uassert.PanicsWithMessage(t, ErrNonCodeCall.Error(), Register)

rlm1 := std.NewCodeRealm(rlm1Path)
std.TestSetRealm(rlm1)
urequire.NotPanics(t, Register)
uassert.PanicsWithMessage(t, ErrAlreadyExists.Error(), Register)

// Find registered items
i, ok := exhibition.items.Get(rlm1Path)
uassert.True(t, ok)

_, ok = exhibition.itemsSorted.Get(i.(*Item).id.String())
uassert.True(t, ok)

// Check paused
std.TestSetRealm(userRealm)
urequire.NotPanics(t, func() { exhibition.Pause() })
uassert.PanicsWithMessage(t, ErrPaused.Error(), Register)
}

func TestUpvote(t *testing.T) {
raw, _ := exhibition.items.Get(rlm1Path)
item := raw.(*Item)

rawSorted, _ := exhibition.itemsSorted.Get(item.id.String())
itemSorted := rawSorted.(*Item)

// 0 upvotes by default
urequire.Equal(t, item.upvote.Size(), 0)

userRealm := std.NewUserRealm(admin)
std.TestSetRealm(userRealm)

urequire.NotPanics(t, func() {
Upvote(rlm1Path)
})

// Check both trees for 1 upvote
uassert.Equal(t, item.upvote.Size(), 1)
uassert.Equal(t, itemSorted.upvote.Size(), 1)

// Check double upvote
uassert.PanicsWithMessage(t, ErrDoubleUpvote.Error(), func() {
Upvote(rlm1Path)
})
}

func TestDownvote(t *testing.T) {
raw, _ := exhibition.items.Get(rlm1Path)
item := raw.(*Item)

rawSorted, _ := exhibition.itemsSorted.Get(item.id.String())
itemSorted := rawSorted.(*Item)

// 0 upvotes by default
urequire.Equal(t, item.downvote.Size(), 0)

userRealm := std.NewUserRealm(admin)
std.TestSetRealm(userRealm)

urequire.NotPanics(t, func() {
Downvote(rlm1Path)
})

// Check both trees for 1 upvote
uassert.Equal(t, item.downvote.Size(), 1)
uassert.Equal(t, itemSorted.downvote.Size(), 1)

// Check double upvote
uassert.PanicsWithMessage(t, ErrDoubleDownvote.Error(), func() {
Downvote(rlm1Path)
})
}

func TestDelete(t *testing.T) {
userRealm := std.NewUserRealm(admin)
std.TestSetRealm(userRealm)
std.TestSetOrigCaller(admin)

uassert.PanicsWithMessage(t, ErrNoSuchItem.Error(), func() {
Delete("nonexistentpkgpath")
})

i, ok := exhibition.items.Get(rlm1Path)
id := i.(*Item).id

uassert.NotPanics(t, func() {
Delete(rlm1Path)
})

uassert.False(t, exhibition.items.Has(rlm1Path))
uassert.False(t, exhibition.itemsSorted.Has(id.String()))
}
Loading
Loading