Skip to content

Commit

Permalink
Merge branch 'master' into optimize-gnolang.ReadMemPackage+other-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
odeke-em authored Jan 31, 2025
2 parents e49d404 + d3774ce commit 6ce2e9e
Show file tree
Hide file tree
Showing 35 changed files with 931 additions and 161 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/gnoland.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ on:
# Changes to examples/ can create failures in gno.land, eg. txtars,
# see: https://github.com/gnolang/gno/pull/3590
- examples/**
# We trigger the testing workflow for changes to the main go.mod,
# since this can affect test results
- go.mod
workflow_dispatch:

jobs:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/gnovm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
paths:
- gnovm/**
- tm2/** # GnoVM has a dependency on TM2 types
# We trigger the testing workflow for changes to the main go.mod,
# since this can affect test results
- go.mod
workflow_dispatch:

jobs:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tm2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
pull_request:
paths:
- tm2/**
# We trigger the testing workflow for changes to the main go.mod,
# since this can affect test results
- go.mod
workflow_dispatch:

jobs:
Expand Down
10 changes: 10 additions & 0 deletions examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ownable2step

import "errors"

var (
ErrNoPendingOwner = errors.New("ownable2step: no pending owner")
ErrUnauthorized = errors.New("ownable2step: caller is not owner")
ErrPendingUnauthorized = errors.New("ownable2step: caller is not pending owner")
ErrInvalidAddress = errors.New("ownable2step: new owner address is invalid")
)
1 change: 1 addition & 0 deletions examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/oxtekgrinder/ownable2step
98 changes: 98 additions & 0 deletions examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ownable2step

import (
"std"
)

const OwnershipTransferEvent = "OwnershipTransfer"

// Ownable2Step is a two-step ownership transfer package
// It allows the current owner to set a new owner and the new owner will need to accept the ownership before it is transferred
type Ownable2Step struct {
owner std.Address
pendingOwner std.Address
}

func New() *Ownable2Step {
return &Ownable2Step{
owner: std.PrevRealm().Addr(),
pendingOwner: "",
}
}

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

// TransferOwnership initiate the transfer of the ownership to a new address by setting the PendingOwner
func (o *Ownable2Step) TransferOwnership(newOwner std.Address) error {
if !o.CallerIsOwner() {
return ErrUnauthorized
}
if !newOwner.IsValid() {
return ErrInvalidAddress
}

o.pendingOwner = newOwner
return nil
}

// AcceptOwnership accepts the pending ownership transfer
func (o *Ownable2Step) AcceptOwnership() error {
if o.pendingOwner.String() == "" {
return ErrNoPendingOwner
}
if std.PrevRealm().Addr() != o.pendingOwner {
return ErrPendingUnauthorized
}

o.owner = o.pendingOwner
o.pendingOwner = ""

return nil
}

// 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 *Ownable2Step) DropOwnership() error {
if !o.CallerIsOwner() {
return ErrUnauthorized
}

prevOwner := o.owner
o.owner = ""

std.Emit(
OwnershipTransferEvent,
"from", prevOwner.String(),
"to", "",
)

return nil
}

// Owner returns the owner address from Ownable
func (o *Ownable2Step) Owner() std.Address {
return o.owner
}

// PendingOwner returns the pending owner address from Ownable2Step
func (o *Ownable2Step) PendingOwner() std.Address {
return o.pendingOwner
}

// CallerIsOwner checks if the caller of the function is the Realm's owner
func (o *Ownable2Step) CallerIsOwner() bool {
return std.PrevRealm().Addr() == o.owner
}

// AssertCallerIsOwner panics if the caller is not the owner
func (o *Ownable2Step) AssertCallerIsOwner() {
if std.PrevRealm().Addr() != o.owner {
panic(ErrUnauthorized)
}
}
156 changes: 156 additions & 0 deletions examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package ownable2step

import (
"std"
"testing"

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

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

func TestNew(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

o := New()
got := o.Owner()
pendingOwner := o.PendingOwner()

uassert.Equal(t, got, alice)
uassert.Equal(t, pendingOwner.String(), "")
}

func TestNewWithAddress(t *testing.T) {
o := NewWithAddress(alice)

got := o.Owner()
pendingOwner := o.PendingOwner()

uassert.Equal(t, got, alice)
uassert.Equal(t, pendingOwner.String(), "")
}

func TestInitiateTransferOwnership(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

o := New()

err := o.TransferOwnership(bob)
urequire.NoError(t, err)

owner := o.Owner()
pendingOwner := o.PendingOwner()

uassert.Equal(t, owner, alice)
uassert.Equal(t, pendingOwner, bob)
}

func TestTransferOwnership(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

o := New()

err := o.TransferOwnership(bob)
urequire.NoError(t, err)

owner := o.Owner()
pendingOwner := o.PendingOwner()

uassert.Equal(t, owner, alice)
uassert.Equal(t, pendingOwner, bob)

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

err = o.AcceptOwnership()
urequire.NoError(t, err)

owner = o.Owner()
pendingOwner = o.PendingOwner()

uassert.Equal(t, owner, bob)
uassert.Equal(t, pendingOwner.String(), "")
}

func TestCallerIsOwner(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

o := New()
unauthorizedCaller := bob

std.TestSetRealm(std.NewUserRealm(unauthorizedCaller))
std.TestSetOrigCaller(unauthorizedCaller)

uassert.False(t, o.CallerIsOwner())
}

func TestDropOwnership(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))

o := New()

err := o.DropOwnership()
urequire.NoError(t, err, "DropOwnership failed")

owner := o.Owner()
uassert.Empty(t, owner, "owner should be empty")
}

// Errors

func TestErrUnauthorized(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigCaller(alice)

o := New()

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

uassert.ErrorContains(t, o.TransferOwnership(alice), ErrUnauthorized.Error())
uassert.ErrorContains(t, o.DropOwnership(), ErrUnauthorized.Error())
}

func TestErrInvalidAddress(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))

o := New()

err := o.TransferOwnership("")
uassert.ErrorContains(t, err, ErrInvalidAddress.Error())

err = o.TransferOwnership("10000000001000000000100000000010000000001000000000")
uassert.ErrorContains(t, err, ErrInvalidAddress.Error())
}

func TestErrNoPendingOwner(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))

o := New()

err := o.AcceptOwnership()
uassert.ErrorContains(t, err, ErrNoPendingOwner.Error())
}

func TestErrPendingUnauthorized(t *testing.T) {
std.TestSetRealm(std.NewUserRealm(alice))

o := New()

err := o.TransferOwnership(bob)
urequire.NoError(t, err)

std.TestSetRealm(std.NewUserRealm(alice))

err = o.AcceptOwnership()
uassert.ErrorContains(t, err, ErrPendingUnauthorized.Error())
}
1 change: 1 addition & 0 deletions examples/gno.land/r/docs/avl_pager_with_params/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/docs/avl_pager_params
86 changes: 86 additions & 0 deletions examples/gno.land/r/docs/avl_pager_with_params/render.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package avl_pager_params

import (
"gno.land/p/demo/avl"
"gno.land/p/demo/avl/pager"
"gno.land/p/demo/seqid"
"gno.land/p/demo/ufmt"
"gno.land/p/moul/realmpath"
)

// We'll keep some demo data in an AVL tree to showcase pagination.
var (
items *avl.Tree
idCounter seqid.ID
)

func init() {
items = avl.NewTree()
// Populate the tree with 15 sample items for demonstration.
for i := 1; i <= 15; i++ {
id := idCounter.Next().String()
items.Set(id, "Some item value: "+id)
}
}

func Render(path string) string {
// 1) Parse the incoming path to split route vs. query.
req := realmpath.Parse(path)
// - req.Path contains everything *before* ? or $ (? - query params, $ - gnoweb params)
// - The remaining part (page=2, size=5, etc.) is not in req.Path.

// 2) If no specific route is provided (req.Path == ""), we’ll show a “home” page
// that displays a list of configs in paginated form.
if req.Path == "" {
return renderHome(path)
}

// 3) If a route *is* provided (e.g. :SomeKey),
// we will interpret it as a request for a specific page.
return renderConfigItem(req.Path)
}

// renderHome shows a paginated list of config items if route == "".
func renderHome(fullPath string) string {
// Create a Pager for our config tree, with a default page size of 5.
p := pager.NewPager(items, 5, false)

// MustGetPageByPath uses the *entire* path (including query parts: ?page=2, etc.)
page := p.MustGetPageByPath(fullPath)

// Start building the output (plain text or markdown).
out := "# AVL Pager + Render paths\n\n"
out += `This realm showcases how to maintain a paginated list while properly parsing render paths.
You can see how a single page can include a paginated element (like the example below), and how clicking
an item can take you to a dedicated page for that specific item.
No matter how you browse through the paginated list, the introductory text (this section) remains the same.
`

out += ufmt.Sprintf("Showing page %d of %d\n\n", page.PageNumber, page.TotalPages)

// List items for this page.
for _, item := range page.Items {
// Link each item to a details page: e.g. ":Config01"
out += ufmt.Sprintf("- [Item %s](/r/docs/avl_pager_params:%s)\n", item.Key, item.Key)
}

// Insert pagination controls (previous/next links, etc.).
out += "\n" + page.Picker() + "\n\n"
out += "### [Go back to r/docs](/r/docs)"

return out
}

// renderConfigItem shows details for a single item, e.g. ":item001".
func renderConfigItem(itemName string) string {
value, ok := items.Get(itemName)
if !ok {
return ufmt.Sprintf("**No item found** for key: %s", itemName)
}

out := ufmt.Sprintf("# Item %s\n\n%s\n\n", itemName, value.(string))
out += "[Go back](/r/docs/avl_pager_params)"
return out
}
2 changes: 2 additions & 0 deletions examples/gno.land/r/docs/docs.gno
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Explore various examples to learn more about Gno functionality and usage.
- [Source](/r/docs/source) - View realm source code.
- [Buttons](/r/docs/buttons) - Add buttons to your realm's render.
- [AVL Pager](/r/docs/avl_pager) - Paginate through AVL tree items.
- [AVL Pager + Render paths](/r/docs/avl_pager_params) - Handle render arguments with pagination.
- [Img Embed](/r/docs/img_embed) - Demonstrates how to embed an image.
- [Optional Render](/r/docs/optional_render) - Render() is optional in realms.
- ...
<!-- meta issue with suggestions: https://github.com/gnolang/gno/issues/3292 -->
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/r/docs/optional_render/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/docs/optional_render
Loading

0 comments on commit 6ce2e9e

Please sign in to comment.