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(boards2): implement next boards version - WIP #2901

Draft
wants to merge 53 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
71c93a7
feat(boardsv2): init (#2900)
ilgooz Oct 4, 2024
baf9821
Merge branch 'master' into devx/feature/boardsv2
ilgooz Oct 4, 2024
1a88d58
feat(boardsv2): experiment API - WIP (#2902)
jeronimoalbi Nov 4, 2024
7408299
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Nov 11, 2024
42d5089
chore: remove `boardsv2` drafts (#3111)
jeronimoalbi Nov 12, 2024
1510a6f
chore: copy `boards` realm to `boards2` (#3110)
jeronimoalbi Nov 12, 2024
4c7d16b
feat: simplify `boards2` implementation (#3115)
jeronimoalbi Nov 13, 2024
8874e8e
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Nov 15, 2024
dd21273
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Nov 19, 2024
1f6d61f
test(boards2): unit tests for `Board` and `Post` types (#3150)
jeronimoalbi Nov 20, 2024
7446197
feat(boards2): update package name to "boards2" (#3184)
x1unix Nov 22, 2024
c31350c
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Nov 25, 2024
4944a7e
feat(boards2): initial permissions support (#3151)
jeronimoalbi Nov 26, 2024
4027d07
feat(boards2): board creation and permissions (#3211)
jeronimoalbi Nov 29, 2024
20fc146
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Dec 18, 2024
75cd4ab
fix: resolve devx boards2 branch issues (#3365)
jeronimoalbi Dec 18, 2024
251a107
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Dec 18, 2024
261e316
feat: permissions support for individual boards (#3386)
jeronimoalbi Dec 23, 2024
0e7d24f
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Dec 23, 2024
b8ac6b0
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 6, 2025
8844f30
feat(boards2): add core flagging logic (#3451)
x1unix Jan 7, 2025
efbfd00
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 7, 2025
3c41887
chore(boards2): remove permission handlers (#3449)
jeronimoalbi Jan 7, 2025
25622db
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 7, 2025
40eead0
feat(boards2): board renaming (#3462)
jeronimoalbi Jan 9, 2025
39e19d3
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 9, 2025
0556dc8
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 15, 2025
4090aac
feat(boardsv2): initialize reposting (#3513)
x1unix Jan 16, 2025
541278b
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 16, 2025
adaafd7
feat(boards2): member role management (#3512)
jeronimoalbi Jan 16, 2025
e60574f
feat(boardsv2): hide flagged comments (#3536)
x1unix Jan 17, 2025
a10d2c7
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 17, 2025
1e24a33
feat(boards2): initial commondao implementation (#3558)
jeronimoalbi Jan 22, 2025
2f99874
feat(boards2): change public delete reply function to soft delete rep…
jeronimoalbi Jan 27, 2025
dc5a753
feat(boards2): improve public realm functions (#3583)
jeronimoalbi Jan 27, 2025
e769fb1
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 27, 2025
48a8042
feat(boards2): pagination (#3586)
x1unix Jan 27, 2025
6592035
feat(boards2): add proposal definition tally support to `commondao` p…
jeronimoalbi Jan 28, 2025
5f3a9f6
chore(boards2): move `commondao` & `boards2` to "nt" namespace (#3618)
jeronimoalbi Jan 28, 2025
69960ec
test(boards2): add missing filetests for board creation (#3622)
jeronimoalbi Jan 28, 2025
33fb889
test(boards2): add missing filetests for board rename (#3625)
jeronimoalbi Jan 28, 2025
90a3c4e
test(boards2): add missing filetests for member invite (#3632)
jeronimoalbi Jan 30, 2025
dc173c2
test(boards2): add missing filetests for member removal (#3633)
jeronimoalbi Jan 30, 2025
63007ab
test(boards2): add missing filetests for member role change (#3636)
jeronimoalbi Jan 30, 2025
bab1153
test(boards2): add missing filetests for member check (#3638)
jeronimoalbi Jan 30, 2025
b00f9e1
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 30, 2025
db1dd43
test(boards2): add missing filetests for thread creation (#3644)
jeronimoalbi Jan 30, 2025
33da5ed
test(boards2): add missing filetests for thread edit (#3649)
jeronimoalbi Jan 31, 2025
d7dcd92
test(boards2): add missing filetests for thread flagging (#3646)
jeronimoalbi Jan 31, 2025
d7740a4
test(boards2): add missing filetests for thread deletion (#3645)
jeronimoalbi Jan 31, 2025
5f67719
test(boards2): add missing filetests for board ID getter by name (#3642)
jeronimoalbi Jan 31, 2025
60334e6
Merge branch 'master' into devx/feature/boardsv2
jeronimoalbi Jan 31, 2025
b983f8f
test(boards2): add missing filetests for reply creation (#3653)
jeronimoalbi Jan 31, 2025
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
225 changes: 225 additions & 0 deletions examples/gno.land/p/nt/commondao/commondao.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package commondao

import (
"errors"
"std"
"time"

"gno.land/p/demo/avl"
"gno.land/p/demo/avl/rotree"
"gno.land/p/demo/seqid"
)

var (
ErrInvalidVoteChoice = errors.New("invalid vote choice")
ErrMemberExists = errors.New("member already exist")
ErrNotMember = errors.New("account is not a member of the DAO")
ErrOverflow = errors.New("next ID overflows uint64")
ErrProposalNotFound = errors.New("proposal not found")
ErrStatusIsNotActive = errors.New("proposal status is not active")
ErrVotingDeadlineNotMet = errors.New("voting deadline not met")
)

type (
// CommonDAO defines a DAO.
CommonDAO struct {
parent *CommonDAO
members *avl.Tree // string(std.Address) -> struct{}
genID seqid.ID
active *avl.Tree // string(proposal ID) -> *Proposal
finished *avl.Tree // string(proposal ID) -> *Proposal
}

// Stats contains proposal voting stats.
Stats struct {
YayVotes int
NayVotes int
Abstained int
}
)

// New creates a new common DAO.
func New(options ...Option) *CommonDAO {
dao := &CommonDAO{
members: avl.NewTree(),
active: avl.NewTree(),
finished: avl.NewTree(),
}
for _, apply := range options {
apply(dao)
}
return dao
}

// Parent returns the parent DAO.
// Null can be returned when DAO has no parent assigned.
func (dao CommonDAO) Parent() *CommonDAO {
return dao.parent
}

// Members returns the list of DAO members.
func (dao CommonDAO) Members() []std.Address {
var members []std.Address
dao.members.Iterate("", "", func(key string, _ interface{}) bool {
members = append(members, std.Address(key))
return false
})
return members
}

// AddMember adds a new member to the DAO.
func (dao *CommonDAO) AddMember(user std.Address) error {
if dao.IsMember(user) {
return ErrMemberExists
}
dao.members.Set(user.String(), struct{}{})
return nil
}

// RemoveMember removes a member from the DAO.
func (dao *CommonDAO) RemoveMember(user std.Address) (removed bool) {
_, removed = dao.members.Remove(user.String())
return removed
}

// IsMember checks if a user is a member of the DAO.
func (dao CommonDAO) IsMember(user std.Address) bool {
return dao.members.Has(user.String())
}

// ActiveProposals returns all active DAO proposals.
func (dao CommonDAO) ActiveProposals() rotree.IReadOnlyTree {
return dao.active
}

// FinishedProposalsi returns all finished DAO proposals.
func (dao CommonDAO) FinishedProposals() rotree.IReadOnlyTree {
return dao.finished
}

// Propose creates a new DAO proposal.
func (dao *CommonDAO) Propose(creator std.Address, d ProposalDefinition) (*Proposal, error) {
id, ok := dao.genID.TryNext()
if !ok {
return nil, ErrOverflow
}

p, err := NewProposal(uint64(id), creator, d)
if err != nil {
return nil, err
}

key := makeProposalKey(p.ID())
dao.active.Set(key, p)
return p, nil
}

// GetActiveProposal returns an active proposal.
func (dao CommonDAO) GetActiveProposal(proposalID uint64) (_ *Proposal, found bool) {
key := makeProposalKey(proposalID)
if v, ok := dao.active.Get(key); ok {
return v.(*Proposal), true
}
return nil, false
}

// GetFinishedProposal returns a finished proposal.
func (dao CommonDAO) GetFinishedProposal(proposalID uint64) (_ *Proposal, found bool) {
key := makeProposalKey(proposalID)
if v, ok := dao.finished.Get(key); ok {
return v.(*Proposal), true
}
return nil, false
}

// Vote submits a new vote for a proposal.
func (dao *CommonDAO) Vote(member std.Address, proposalID uint64, c VoteChoice) error {
if c != ChoiceYes && c != ChoiceNo && c != ChoiceAbstain {
return ErrInvalidVoteChoice
}

if !dao.IsMember(member) {
return ErrNotMember
}

p, found := dao.GetActiveProposal(proposalID)
if !found {
return ErrProposalNotFound
}
return p.record.AddVote(member, c)
}

func (dao *CommonDAO) Tally(p *Proposal) Stats {
// Initialize stats considering only yes/no votes
record := p.VotingRecord()
stats := Stats{
YayVotes: record.VoteCount(ChoiceYes),
NayVotes: record.VoteCount(ChoiceNo),
}
votesCount := stats.YayVotes + stats.NayVotes
membersCount := len(dao.Members())
stats.Abstained = membersCount - votesCount

percentage := float64(votesCount) / float64(membersCount)
if percentage < p.Quorum() {
p.status = StatusFailed
p.statusReason = "low participation"
return stats
}

if !p.Definition().Tally(record, membersCount) {
p.status = StatusFailed
p.statusReason = "no consensus"
}

return stats
}

// Execute executes a proposal.
func (dao *CommonDAO) Execute(proposalID uint64) error {
p, found := dao.GetActiveProposal(proposalID)
if !found {
return ErrProposalNotFound
}

if p.Status() != StatusActive {
return ErrStatusIsNotActive
}

if time.Now().Before(p.VotingDeadline()) {
return ErrVotingDeadlineNotMet
}

// Validate proposal before executing it
def := p.Definition()
err := def.Validate()
if err != nil {
p.status = StatusFailed
p.statusReason = err.Error()
} else {
// Tally votes and update proposal status
dao.Tally(p)

// Execute proposal only when the majority vote wins
if p.Status() != StatusFailed {
err = def.Execute()
if err != nil {
p.status = StatusFailed
p.statusReason = err.Error()
} else {
p.status = StatusExecuted
}
}
}

// Whichever the outcome of the validation, tallying
// and execution consider the proposal finished.
key := makeProposalKey(p.id)
dao.active.Remove(key)
dao.finished.Set(key, p)
return err
}

func makeProposalKey(id uint64) string {
return seqid.ID(id).String()
}
Loading
Loading