govdao.gno
4.95 Kb · 194 lines
1package impl
2
3import (
4 "chain"
5 "chain/runtime"
6 "errors"
7
8 "gno.land/p/nt/ufmt/v0"
9 "gno.land/r/gov/dao"
10 "gno.land/r/gov/dao/v3/memberstore"
11)
12
13var ErrMemberNotFound = errors.New("member not found")
14
15type GovDAO struct {
16 pss ProposalsStatuses
17 render *render
18}
19
20func NewGovDAO() *GovDAO {
21 pss := NewProposalsStatuses()
22 d := &GovDAO{
23 pss: pss,
24 }
25
26 d.render = NewRender(d)
27
28 // There was no realm, from main(), so it succeeded, And
29 // when returning, there was no finalization. We don't
30 // finalize anyways because there wasn't a realm boundary.
31 // XXX make filetest main package a realm.
32 //
33 // filetest.init() ->
34 // v3/init.Init() ->
35 // NewGovDAO() ->
36 // returns an unsaved DAO NOTE NO REALM!
37 // dao.UpdateImpl =>
38 // saves dao under
39 //
40 // r/gov/dao.CrossPropposal() ->
41 // proposals.SetProposal(),
42 // that proposal lives in r/gov/dao.
43 // r/gov/dao.ExecuteProposal() ->
44 // g.PreExecuteProposal() ->
45 // XXX g.test = 1 fails, owned by gov/dao.
46 //
47 //
48 func(cur realm) {
49 // TODO: replace with future attach()
50 _govdao = d
51 }(cross)
52
53 return d
54}
55
56// Setting this to a global variable forces attaching the GovDAO struct to this
57// realm. TODO replace with future `attach()`.
58var _govdao *GovDAO
59
60func (g *GovDAO) PreCreateProposal(r dao.ProposalRequest) (address, error) {
61 if !g.isValidCall() {
62 return "", errors.New(ufmt.Sprintf("proposal creation must be done directly by a user or through the r/gov/dao proxy. current realm: %v; previous realm: %v",
63 runtime.CurrentRealm(), runtime.PreviousRealm()))
64 }
65
66 // Verify that the one creating the proposal is a member.
67 caller := runtime.OriginCaller()
68 mem, _ := getMembers(cross).GetMember(caller)
69 if mem == nil {
70 return caller, errors.New("only members can create new proposals")
71 }
72
73 return caller, nil
74}
75
76func (g *GovDAO) PostCreateProposal(r dao.ProposalRequest, pid dao.ProposalID) {
77 // Tiers Allowed to Vote
78 tatv := []string{memberstore.T1, memberstore.T2, memberstore.T3}
79 switch v := r.Filter().(type) {
80 case FilterByTier:
81 // only members from T1 are allowed to vote when adding new members to T1
82 if v.Tier == memberstore.T1 {
83 tatv = []string{memberstore.T1}
84 }
85 // only members from T1 and T2 are allowed to vote when adding new members to T2
86 if v.Tier == memberstore.T2 {
87 tatv = []string{memberstore.T1, memberstore.T2}
88 }
89 }
90 g.pss.Set(pid.String(), newProposalStatus(tatv))
91}
92
93func (g *GovDAO) VoteOnProposal(r dao.VoteRequest) error {
94 if !g.isValidCall() {
95 return errors.New("proposal voting must be done directly by a user")
96 }
97
98 caller := runtime.OriginCaller()
99 mem, tie := getMembers(cross).GetMember(caller)
100 if mem == nil {
101 return ErrMemberNotFound
102 }
103
104 status := g.pss.GetStatus(r.ProposalID)
105 if status == nil {
106 return errors.New("proposal not found")
107 }
108
109 if status.Denied || status.Accepted {
110 return errors.New(ufmt.Sprintf("proposal closed. Accepted: %v", status.Accepted))
111 }
112
113 if !status.IsAllowed(tie) {
114 return errors.New("member on specified tier is not allowed to vote on this proposal")
115 }
116
117 mVoted, _ := status.AllVotes.GetMember(caller)
118 if mVoted != nil {
119 return errors.New("already voted on proposal")
120 }
121
122 switch r.Option {
123 case dao.YesVote:
124 status.AllVotes.SetMember(tie, caller, mem)
125 status.YesVotes.SetMember(tie, caller, mem)
126 case dao.NoVote:
127 status.AllVotes.SetMember(tie, caller, mem)
128 status.NoVotes.SetMember(tie, caller, mem)
129 default:
130 return errors.New("voting can only be YES or NO")
131 }
132
133 return nil
134}
135
136func (g *GovDAO) PreGetProposal(pid dao.ProposalID) error {
137 return nil
138}
139
140func (g *GovDAO) PostGetProposal(pid dao.ProposalID, p *dao.Proposal) error {
141 return nil
142}
143
144func (g *GovDAO) PreExecuteProposal(pid dao.ProposalID) (bool, error) {
145 if !g.isValidCall() {
146 return false, errors.New("proposal execution must be done directly by a user")
147 }
148 status := g.pss.GetStatus(pid)
149 if status.Denied || status.Accepted {
150 return false, errors.New(ufmt.Sprintf("proposal already executed. Accepted: %v", status.Accepted))
151 }
152
153 if status.YesPercent() >= law.Supermajority {
154 status.Accepted = true
155 return true, nil
156 }
157
158 if status.NoPercent() >= law.Supermajority {
159 status.Denied = true
160 return false, nil
161 }
162
163 return false, errors.New(ufmt.Sprintf("proposal didn't reach supermajority yet: %v", law.Supermajority))
164}
165
166func (g *GovDAO) Render(pkgPath string, path string) string {
167 return g.render.Render(pkgPath, path)
168}
169
170func (g *GovDAO) isValidCall() bool {
171 // We need to verify two cases:
172 // 1: r/gov/dao (proxy) functions called directly by an user
173 // 2: r/gov/dao/v3/impl methods called directly by an user
174
175 // case 1
176 if runtime.CurrentRealm().PkgPath() == "gno.land/r/gov/dao" {
177 // called directly by an user through MsgCall
178 if runtime.PreviousRealm().IsUser() {
179 return true
180 }
181 isMsgRun := chain.PackageAddress(runtime.PreviousRealm().PkgPath()) == runtime.OriginCaller()
182 // called directly by an user through MsgRun
183 if isMsgRun {
184 return true
185 }
186 }
187
188 // case 2
189 if runtime.CurrentRealm().IsUser() {
190 return true
191 }
192
193 return false
194}