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: adding home realm for r/n2p5/home and supporting packages #3183

Merged
merged 6 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
84 changes: 84 additions & 0 deletions examples/gno.land/p/n2p5/chonk/chonk.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Package chonk provides a simple way to store arbitrarily large strings
// in a linked list across transactions for efficient storage and retrieval.
// A Chonk support three operations: Add, Flush, and Scanner.
// - Add appends a string to the Chonk.
// - Flush clears the Chonk.
// - Scanner is used to iterate over the chunks in the Chonk.
package chonk

// Chonk is a linked list string storage and
// retrieval system for fine bois.
type Chonk struct {
first *chunk
last *chunk
}

// chunk is a linked list node for Chonk
type chunk struct {
text string
next *chunk
}

// New creates a reference to a new Chonk
func New() *Chonk {
return &Chonk{}
}

// Add appends a string to the Chonk. If the Chonk is empty,
// the string will be the first and last chunk. Otherwise,
// the string will be appended to the end of the Chonk.
func (c *Chonk) Add(text string) {
next := &chunk{text: text}
if c.first == nil {
c.first = next
c.last = next
return
}
c.last.next = next
c.last = next
}

// Flush clears the Chonk by setting the first and last
// chunks to nil. This will allow the garbage collector to
// free the memory used by the Chonk.
func (c *Chonk) Flush() {
c.first = nil
c.last = nil
}

// Scanner returns a new Scanner for the Chonk. The Scanner
// is used to iterate over the chunks in the Chonk.
func (c *Chonk) Scanner() *Scanner {
return &Scanner{
next: c.first,
}
}

// Scanner is a simple string scanner for Chonk. It is used
// to iterate over the chunks in a Chonk from first to last.
type Scanner struct {
current *chunk
next *chunk
}

// Scan advances the scanner to the next chunk. It returns
// true if there is a next chunk, and false if there is not.
func (s *Scanner) Scan() bool {
if s.next != nil {
s.current = s.next
s.next = s.next.next
return true
}
return false
}

// Text returns the current chunk. It is only valid to call
// this method after a call to Scan returns true. Expected usage:
//
// scanner := chonk.Scanner()
// for scanner.Scan() {
// fmt.Println(scanner.Text())
// }
func (s *Scanner) Text() string {
return s.current.text
}
54 changes: 54 additions & 0 deletions examples/gno.land/p/n2p5/chonk/chonk_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package chonk

import (
"testing"
)

func TestChonk(t *testing.T) {
t.Parallel()
c := New()
testTable := []struct {
name string
chunks []string
}{
{
name: "empty",
chunks: []string{},
},
{
name: "single chunk",
chunks: []string{"a"},
},
{
name: "multiple chunks",
chunks: []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
},
{
name: "multiline chunks",
chunks: []string{"1a\nb\nc\n\n", "d\ne\nf", "g\nh\ni", "j\nk\nl\n\n\n\n"},
},
{
name: "empty",
chunks: []string{},
},
}
testChonk := func(t *testing.T, c *Chonk, chunks []string) {
for _, chunk := range chunks {
c.Add(chunk)
}
scanner := c.Scanner()
i := 0
for scanner.Scan() {
if scanner.Text() != chunks[i] {
t.Errorf("expected %s, got %s", chunks[i], scanner.Text())
}
i++
}
}
for _, test := range testTable {
t.Run(test.name, func(t *testing.T) {
testChonk(t, c, test.chunks)
c.Flush()
})
}
}
1 change: 1 addition & 0 deletions examples/gno.land/p/n2p5/chonk/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/n2p5/chonk
7 changes: 7 additions & 0 deletions examples/gno.land/p/n2p5/mgroup/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/p/n2p5/mgroup

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
)
184 changes: 184 additions & 0 deletions examples/gno.land/p/n2p5/mgroup/mgroup.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Package mgroup is a simple managed group managing ownership and membership
// for authorization in gno realms. The ManagedGroup struct is used to manage
// the owner, backup owners, and members of a group. The owner is the primary
// owner of the group and can add and remove backup owners and members. Backup
// owners can claim ownership of the group. This is meant to provide backup
// accounts for the owner in case the owner account is lost or compromised.
// Members are used to authorize actions across realms.
package mgroup

import (
"errors"
"std"

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

var (
ErrCannotRemoveOwner = errors.New("mgroup: cannot remove owner")
ErrNotBackupOwner = errors.New("mgroup: not a backup owner")
ErrNotMember = errors.New("mgroup: not a member")
ErrInvalidAddress = errors.New("mgroup: address is invalid")
)

type ManagedGroup struct {
owner *ownable.Ownable
backupOwners *avl.Tree
members *avl.Tree
}

// New creates a new ManagedGroup with the owner set to the provided address.
// The owner is automatically added as a backup owner and member of the group.
func New(ownerAddress std.Address) *ManagedGroup {
g := &ManagedGroup{
owner: ownable.NewWithAddress(ownerAddress),
backupOwners: avl.NewTree(),
members: avl.NewTree(),
}
g.AddBackupOwner(ownerAddress)
g.AddMember(ownerAddress)
return g
}

// AddBackupOwner adds a backup owner to the group by std.Address.
// If the caller is not the owner, an error is returned.
func (g *ManagedGroup) AddBackupOwner(addr std.Address) error {
if err := g.owner.CallerIsOwner(); err != nil {
return err
}
if !addr.IsValid() {
return ErrInvalidAddress
}
g.backupOwners.Set(addr.String(), struct{}{})
return nil
}

// RemoveBackupOwner removes a backup owner from the group by std.Address.
// The owner cannot be removed. If the caller is not the owner, an error is returned.
func (g *ManagedGroup) RemoveBackupOwner(addr std.Address) error {
if err := g.owner.CallerIsOwner(); err != nil {
return err
}
if !addr.IsValid() {
return ErrInvalidAddress
}
if addr == g.Owner() {
return ErrCannotRemoveOwner
}
g.backupOwners.Remove(addr.String())
return nil
}

// ClaimOwnership allows a backup owner to claim ownership of the group.
// If the caller is not a backup owner, an error is returned.
// The caller is automatically added as a member of the group.
func (g *ManagedGroup) ClaimOwnership() error {
caller := std.PrevRealm().Addr()
// already owner, skip
if caller == g.Owner() {
return nil
}
if !g.IsBackupOwner(caller) {
return ErrNotMember
}
g.owner = ownable.NewWithAddress(caller)
g.AddMember(caller)
return nil
}

// AddMember adds a member to the group by std.Address.
// If the caller is not the owner, an error is returned.
func (g *ManagedGroup) AddMember(addr std.Address) error {
if err := g.owner.CallerIsOwner(); err != nil {
return err
}
if !addr.IsValid() {
return ErrInvalidAddress
}
g.members.Set(addr.String(), struct{}{})
return nil
}

// RemoveMember removes a member from the group by std.Address.
// The owner cannot be removed. If the caller is not the owner,
// an error is returned.
func (g *ManagedGroup) RemoveMember(addr std.Address) error {
if err := g.owner.CallerIsOwner(); err != nil {
return err
}
if !addr.IsValid() {
return ErrInvalidAddress
}
if addr == g.Owner() {
return ErrCannotRemoveOwner
}
g.members.Remove(addr.String())
return nil
}

// MemberCount returns the number of members in the group.
func (g *ManagedGroup) MemberCount() int {
return g.members.Size()
}

// BackupOwnerCount returns the number of backup owners in the group.
func (g *ManagedGroup) BackupOwnerCount() int {
return g.backupOwners.Size()
}

// IsMember checks if an address is a member of the group.
func (g *ManagedGroup) IsMember(addr std.Address) bool {
return g.members.Has(addr.String())
}

// IsBackupOwner checks if an address is a backup owner in the group.
func (g *ManagedGroup) IsBackupOwner(addr std.Address) bool {
return g.backupOwners.Has(addr.String())
}

// Owner returns the owner of the group.
func (g *ManagedGroup) Owner() std.Address {
return g.owner.Owner()
}

// BackupOwners returns a slice of all backup owners in the group, using the underlying
// avl.Tree to iterate over the backup owners. If you have a large group, you may
// want to use BackupOwnersWithOffset to iterate over backup owners in chunks.
func (g *ManagedGroup) BackupOwners() []string {
return g.BackupOwnersWithOffset(0, g.BackupOwnerCount())
}

// Members returns a slice of all members in the group, using the underlying
// avl.Tree to iterate over the members. If you have a large group, you may
// want to use MembersWithOffset to iterate over members in chunks.
func (g *ManagedGroup) Members() []string {
return g.MembersWithOffset(0, g.MemberCount())
}

// BackupOwnersWithOffset returns a slice of backup owners in the group, using the underlying
// avl.Tree to iterate over the backup owners. The offset and count parameters allow you
// to iterate over backup owners in chunks to support patterns such as pagination.
func (g *ManagedGroup) BackupOwnersWithOffset(offset, count int) []string {
return sliceWithOffset(g.backupOwners, offset, count)
}

// MembersWithOffset returns a slice of members in the group, using the underlying
// avl.Tree to iterate over the members. The offset and count parameters allow you
// to iterate over members in chunks to support patterns such as pagination.
func (g *ManagedGroup) MembersWithOffset(offset, count int) []string {
return sliceWithOffset(g.members, offset, count)
}

// sliceWithOffset is a helper function to iterate over an avl.Tree with an offset and count.
func sliceWithOffset(t *avl.Tree, offset, count int) []string {
var result []string
t.IterateByOffset(offset, count, func(k string, _ interface{}) bool {
if k == "" {
return true
}
result = append(result, k)
return false
})
return result
}
Loading
Loading