proposals.gno
2.88 Kb ยท 126 lines
1package daokit
2
3import (
4 "chain/runtime"
5 "errors"
6 "time"
7
8 "gno.land/p/nt/avl"
9 "gno.land/p/nt/seqid"
10 "gno.land/p/onbloc/json"
11 "gno.land/p/samcrew/daocond"
12)
13
14type ProposalStatus int
15
16const (
17 ProposalStatusOpen ProposalStatus = iota
18 ProposalStatusPassed
19 ProposalStatusExecuted
20)
21
22func (s ProposalStatus) String() string {
23 switch s {
24 case ProposalStatusOpen:
25 return "Open"
26 case ProposalStatusPassed:
27 return "Passed"
28 case ProposalStatusExecuted:
29 return "Executed"
30 default:
31 return "Unknown"
32 }
33}
34
35type Proposal struct {
36 ID seqid.ID
37 Title string
38 Description string
39 CreatedAt time.Time
40 CreatedHeight int64
41 ProposerID string
42 Condition daocond.Condition
43 Action Action
44 Status ProposalStatus
45 ExecutorID string
46 ExecutedAt time.Time
47 Ballot daocond.Ballot
48}
49
50type ProposalsStore struct {
51 Tree *avl.Tree // int -> Proposal
52 genID seqid.ID
53}
54
55type ProposalRequest struct {
56 Title string
57 Description string
58 Action Action
59}
60
61func NewProposalsStore() *ProposalsStore {
62 return &ProposalsStore{
63 Tree: avl.NewTree(),
64 }
65}
66
67func (p *ProposalsStore) newProposal(proposer string, req ProposalRequest, condition daocond.Condition) *Proposal {
68 id := p.genID.Next()
69 proposal := &Proposal{
70 ID: id,
71 Title: req.Title,
72 Description: req.Description,
73 ProposerID: proposer,
74 Status: ProposalStatusOpen,
75 Action: req.Action,
76 Condition: condition,
77 Ballot: daocond.NewBallot(),
78 CreatedAt: time.Now(),
79 CreatedHeight: runtime.ChainHeight(),
80 }
81 p.Tree.Set(id.String(), proposal)
82 return proposal
83}
84
85func (p *ProposalsStore) GetProposal(id uint64) *Proposal {
86 value, ok := p.Tree.Get(seqid.ID(id).String())
87 if !ok {
88 return nil
89 }
90 proposal := value.(*Proposal)
91 return proposal
92}
93
94func (p *Proposal) UpdateStatus() {
95 conditionsAreMet := p.Condition.Eval(p.Ballot)
96 if p.Status == ProposalStatusOpen && conditionsAreMet {
97 p.Status = ProposalStatusPassed
98 }
99}
100
101func (p *ProposalsStore) GetProposalsJSON() string {
102 props := make([]*json.Node, 0, p.Tree.Size())
103 // XXX: pagination
104 p.Tree.Iterate("", "", func(key string, value interface{}) bool {
105 prop, ok := value.(*Proposal)
106 if !ok {
107 panic(errors.New("unexpected invalid proposal type"))
108 }
109 prop.UpdateStatus()
110 props = append(props, json.ObjectNode("", map[string]*json.Node{
111 "id": json.NumberNode("", float64(prop.ID)),
112 "title": json.StringNode("", prop.Title),
113 "description": json.StringNode("", prop.Description),
114 "proposer": json.StringNode("", prop.ProposerID),
115 "status": json.StringNode("", prop.Status.String()),
116 "startHeight": json.NumberNode("", float64(prop.CreatedHeight)),
117 "signal": json.NumberNode("", prop.Condition.Signal(prop.Ballot)),
118 }))
119 return false
120 })
121 bz, err := json.Marshal(json.ArrayNode("", props))
122 if err != nil {
123 panic(err)
124 }
125 return string(bz)
126}