Search Apps Documentation Source Content File Folder Download Copy Actions Download

proposals.gno

3.97 Kb · 153 lines
  1package daokit
  2
  3// Handles DAO governance through proposal voting and execution.
  4// They contain actions that run when voting requirements are satisfied.
  5//
  6// Common use cases: member management, treasury operations, rule updates.
  7
  8import (
  9	"chain/runtime"
 10	"errors"
 11	"time"
 12
 13	"gno.land/p/nt/avl/v0"
 14	"gno.land/p/nt/seqid/v0"
 15	"gno.land/p/onbloc/json"
 16	"gno.land/p/samcrew/daocond"
 17)
 18
 19type ProposalStatus int
 20
 21const (
 22	ProposalStatusOpen     ProposalStatus = iota // Currently accepting votes
 23	ProposalStatusPassed                         // Met conditions, ready for execution
 24	ProposalStatusExecuted                       // Successfully executed
 25)
 26
 27func (s ProposalStatus) String() string {
 28	switch s {
 29	case ProposalStatusOpen:
 30		return "Open"
 31	case ProposalStatusPassed:
 32		return "Passed"
 33	case ProposalStatusExecuted:
 34		return "Executed"
 35	default:
 36		return "Unknown"
 37	}
 38}
 39
 40type Proposal struct {
 41	ID            seqid.ID
 42	Title         string
 43	Description   string
 44	CreatedAt     time.Time
 45	CreatedHeight int64
 46	ProposerID    string
 47
 48	Action     Action            // What to execute if passed
 49	Condition  daocond.Condition // Voting conditions for this proposal
 50	Status     ProposalStatus
 51	ExecutorID string
 52	ExecutedAt time.Time
 53
 54	Ballot daocond.Ballot // All votes cast on this proposal
 55}
 56
 57type ProposalsStore struct {
 58	Tree  *avl.Tree // int -> Proposal
 59	genID seqid.ID
 60}
 61
 62// Data needed to create a new proposal.
 63type ProposalRequest struct {
 64	Title       string
 65	Description string
 66	Action      Action
 67}
 68
 69func NewProposalsStore() *ProposalsStore {
 70	return &ProposalsStore{
 71		Tree: avl.NewTree(),
 72	}
 73}
 74
 75func (p *ProposalsStore) GetProposal(id uint64) *Proposal {
 76	value, ok := p.Tree.Get(seqid.ID(id).String())
 77	if !ok {
 78		return nil
 79	}
 80	proposal := value.(*Proposal)
 81	return proposal
 82}
 83
 84// Returns all proposals that match the given filter function.
 85func (p *ProposalsStore) GetProposals(filter func(Proposal) bool) *ProposalsStore {
 86	proposals := NewProposalsStore()
 87	p.Tree.Iterate("", "", func(key string, value interface{}) bool {
 88		prop, ok := value.(*Proposal)
 89		if !ok {
 90			panic(errors.New("unexpected invalid proposal type"))
 91		}
 92		prop.UpdateStatus() // XXX: costly and probably insecure
 93		if filter(*prop) {
 94			proposals.Tree.Set(key, prop)
 95		}
 96		return false
 97	})
 98	return proposals
 99}
100
101// Updates proposal status based on current voting condition results.
102func (p *Proposal) UpdateStatus() {
103	conditionsAreMet := p.Condition.Eval(p.Ballot)
104	if p.Status == ProposalStatusOpen && conditionsAreMet {
105		p.Status = ProposalStatusPassed
106	}
107}
108
109// Returns all proposals as a JSON string.
110func (p *ProposalsStore) GetProposalsJSON() string {
111	props := make([]*json.Node, 0, p.Tree.Size())
112	// XXX: pagination
113	p.Tree.Iterate("", "", func(key string, value interface{}) bool {
114		prop, ok := value.(*Proposal)
115		if !ok {
116			panic(errors.New("unexpected invalid proposal type"))
117		}
118		prop.UpdateStatus()
119		props = append(props, json.ObjectNode("", map[string]*json.Node{
120			"id":          json.NumberNode("", float64(prop.ID)),
121			"title":       json.StringNode("", prop.Title),
122			"description": json.StringNode("", prop.Description),
123			"proposer":    json.StringNode("", prop.ProposerID),
124			"status":      json.StringNode("", prop.Status.String()),
125			"startHeight": json.NumberNode("", float64(prop.CreatedHeight)),
126			"signal":      json.NumberNode("", prop.Condition.Signal(prop.Ballot)),
127		}))
128		return false
129	})
130	bz, err := json.Marshal(json.ArrayNode("", props))
131	if err != nil {
132		panic(err)
133	}
134	return string(bz)
135}
136
137func (p *ProposalsStore) newProposal(proposer string, req ProposalRequest, condition daocond.Condition) *Proposal {
138	id := p.genID.Next()
139	proposal := &Proposal{
140		ID:            id,
141		Title:         req.Title,
142		Description:   req.Description,
143		ProposerID:    proposer,
144		Status:        ProposalStatusOpen,
145		Action:        req.Action,
146		Condition:     condition,
147		Ballot:        daocond.NewBallot(),
148		CreatedAt:     time.Now(),
149		CreatedHeight: runtime.ChainHeight(),
150	}
151	p.Tree.Set(id.String(), proposal)
152	return proposal
153}