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: add btree_dao to demo #3382

Closed
Closed
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
196 changes: 196 additions & 0 deletions examples/gno.land/r/demo/btree_dao/btree_dao.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package btree_dao

import (
"errors"
"std"
"strings"
"time"

"gno.land/p/demo/btree"
"gno.land/p/demo/grc/grc721"
"gno.land/p/demo/ufmt"
"gno.land/p/moul/md"
)

// RegistrationDetails holds the details of a user's registration
type RegistrationDetails struct {
Address std.Address
RegTime time.Time
UserBTree *btree.BTree
NFTID int
}

// Less implements the Record interface for RegistrationDetails
// It compares based on the time of registration
func (rd *RegistrationDetails) Less(than btree.Record) bool {
other := than.(*RegistrationDetails)
return rd.RegTime.Before(other.RegTime)
}

var (
dao = grc721.NewBasicNFT("BTree DAO", "BTDAO")
tokenID = 0
members = btree.New()
)

// PlantTree allows a user to plant a B-Tree in the DAO forest
func PlantTree(userBTree *btree.BTree) error {
return plantImpl(userBTree, "")
}

// PlantSeed allows a user to register as a seed with a message
func PlantSeed(message string) error {
return plantImpl(nil, message)
}

// plantImpl is the internal implementation that handles both trees and seeds
func plantImpl(userBTree *btree.BTree, seedMessage string) error {
// Get the caller's address
var userAddress std.Address
if std.PrevRealm().IsUser() {
userAddress = std.PrevRealm().Addr()
} else {
userAddress = std.GetOrigCaller()
}

var nftID string
var regDetails *RegistrationDetails

if userBTree != nil {
// Handle tree planting
var treeExists bool
members.Ascend(func(record btree.Record) bool {
regDetails := record.(*RegistrationDetails)
if regDetails.UserBTree == userBTree {
treeExists = true
return false
}
return true
})
if treeExists {
return errors.New("tree is already planted in the forest")
}

if userBTree.Len() == 0 {
return errors.New("cannot plant an empty tree")
}

nftID = ufmt.Sprintf("%d", tokenID)
regDetails = &RegistrationDetails{
Address: userAddress,
RegTime: time.Now(),
UserBTree: userBTree,
NFTID: tokenID,
}
} else {
// Handle seed planting
if seedMessage == "" {
return errors.New("seed message cannot be empty")
}
nftID = "seed_" + ufmt.Sprintf("%d", tokenID)
regDetails = &RegistrationDetails{
Address: userAddress,
RegTime: time.Now(),
UserBTree: nil,
NFTID: tokenID,
}
}

// Mint an NFT to the user
err := dao.Mint(userAddress, grc721.TokenID(nftID))
if err != nil {
return err
}

members.Insert(regDetails)
tokenID++
return nil
}

// Render generates a Markdown representation of the DAO members,
// displaying the latest 10 members and the first 3 members as "OGs".
func Render(path string) string {
var latestMembers []string
var ogMembers []string

// Get total size and first member
totalSize := members.Len()
biggestTree := 0
if maxMember := members.Max(); maxMember != nil {
if userBTree := maxMember.(*RegistrationDetails).UserBTree; userBTree != nil {
biggestTree = userBTree.Len()
}
}

// Collect the latest 10 members
members.Descend(func(record btree.Record) bool {
if len(latestMembers) < 10 {
regDetails := record.(*RegistrationDetails)
addr := regDetails.Address
nftList := ""
balance, err := dao.BalanceOf(addr)
if err == nil && balance > 0 {
nftList = " (NFTs: "
for i := uint64(0); i < balance; i++ {
if i > 0 {
nftList += ", "
}
if regDetails.UserBTree == nil {
nftList += "🌱#" + ufmt.Sprintf("seed_%d", regDetails.NFTID) // Seed emoji for seeds
} else {
nftList += "🌳#" + ufmt.Sprintf("%d", regDetails.NFTID) // Tree emoji for tree planters
}
}
nftList += ")"
}
latestMembers = append(latestMembers, string(addr)+nftList)
return true
}
return false
})

// Collect the first 3 members (OGs)
members.Ascend(func(record btree.Record) bool {
if len(ogMembers) < 3 {
regDetails := record.(*RegistrationDetails)
addr := regDetails.Address
nftList := ""
balance, err := dao.BalanceOf(addr)
if err == nil && balance > 0 {
nftList = " (NFTs: "
for i := uint64(0); i < balance; i++ {
if i > 0 {
nftList += ", "
}
if regDetails.UserBTree == nil {
nftList += "🌱#" + ufmt.Sprintf("seed_%d", regDetails.NFTID) // Seed emoji for seeds
} else {
nftList += "🌳#" + ufmt.Sprintf("%d", regDetails.NFTID) // Tree emoji for tree planters
}
}
nftList += ")"
}
ogMembers = append(ogMembers, string(addr)+nftList)
return true
}
return false
})

// Use the md package to format the output
var sb strings.Builder

sb.WriteString(md.H1("B-Tree DAO Members"))
sb.WriteString(md.H2("Total NFTs Minted"))
sb.WriteString(ufmt.Sprintf("Total NFTs minted: %d\n\n", dao.TokenCount()))
sb.WriteString(md.H2("Member Stats"))
sb.WriteString(ufmt.Sprintf("Total members: %d\n", totalSize))
if biggestTree > 0 {
sb.WriteString(ufmt.Sprintf("Biggest tree size: %d\n", biggestTree))
}
sb.WriteString(md.H2("OG Members"))
sb.WriteString(md.BulletList(ogMembers))
sb.WriteString(md.H2("Latest Members"))
sb.WriteString(md.BulletList(latestMembers))

return sb.String()
}
92 changes: 92 additions & 0 deletions examples/gno.land/r/demo/btree_dao/btree_dao_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package btree_dao

import (
"std"
"testing"
"time"

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

func setupTest() {
std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y"))
members = btree.New()
}

type TestElement struct {
value int
}

func (te *TestElement) Less(than btree.Record) bool {
return te.value < than.(*TestElement).value
}

func TestPlantTree(t *testing.T) {
setupTest()

tree := btree.New()
elements := []int{30, 10, 50, 20, 40}
for _, val := range elements {
tree.Insert(&TestElement{value: val})
}

err := PlantTree(tree)
urequire.NoError(t, err, "Should successfully plant a valid tree")

found := false
members.Ascend(func(record btree.Record) bool {
regDetails := record.(*RegistrationDetails)
if regDetails.UserBTree == tree {
found = true
return false
}
return true
})
uassert.True(t, found, "Tree should be registered in members")

err = PlantTree(tree)
uassert.Error(t, err, "Should not allow planting the same tree twice")

emptyTree := btree.New()
err = PlantTree(emptyTree)
uassert.Error(t, err, "Should not allow planting an empty tree")
}

func TestPlantSeed(t *testing.T) {
setupTest()

err := PlantSeed("Hello DAO!")
urequire.NoError(t, err, "Should successfully plant a seed with valid message")

found := false
members.Ascend(func(record btree.Record) bool {
regDetails := record.(*RegistrationDetails)
if regDetails.UserBTree == nil {
found = true
return false
}
return true
})
uassert.True(t, found, "Seed should be registered in members")

err = PlantSeed("")
uassert.Error(t, err, "Should not allow planting seed with empty message")
}

func TestRegistrationDetailsOrdering(t *testing.T) {
setupTest()

rd1 := &RegistrationDetails{
Address: std.Address("test1"),
RegTime: time.Now(),
}
rd2 := &RegistrationDetails{
Address: std.Address("test2"),
RegTime: time.Now().Add(time.Hour),
}

uassert.True(t, rd1.Less(rd2), "Earlier registration should be less than later one")
uassert.False(t, rd2.Less(rd1), "Later registration should not be less than earlier one")
}
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/btree_dao/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/demo/btree_dao
Loading