proxy.gno
5.74 Kb · 206 lines
1package dao
2
3import (
4 "chain"
5 "errors"
6 "strconv"
7
8 "gno.land/p/nt/ufmt/v0"
9)
10
11// dao is the actual govDAO implementation, having all the needed business logic
12var dao DAO
13
14// allowedDAOs contains realms that can be used to update the actual govDAO implementation,
15// and validate Proposals.
16// This is like that to be able to rollback using a previous govDAO implementation in case
17// the latest implementation has a breaking bug. After a test period, a proposal can be
18// executed to remove all previous govDAOs implementations and leave the last one.
19var allowedDAOs []string
20
21// proposals contains all the proposals in history.
22var proposals *Proposals = NewProposals()
23
24// Render calls directly to Render's DAO implementation.
25// This allows to have this realm as the main entry point for everything.
26func Render(cur realm, p string) string {
27 if dao == nil {
28 return "DAO not initialized"
29 }
30 return dao.Render(cross(cur), cur.PkgPath(), p)
31}
32
33// MustCreateProposal is an utility method that does the same as CreateProposal,
34// but instead of erroing if something happens, it panics.
35func MustCreateProposal(cur realm, r ProposalRequest) ProposalID {
36 pid, err := CreateProposal(cur, r)
37 if err != nil {
38 panic(err.Error())
39 }
40
41 return pid
42}
43
44// ExecuteProposal will try to execute the proposal with the provided ProposalID.
45// If the proposal was denied, it will return false. If the proposal is correctly
46// executed, it will return true. If something happens this function will panic.
47func ExecuteProposal(cur realm, pid ProposalID) bool {
48 return executeProposal(cur, pid, false)
49}
50
51// ExecuteOrRejectProposal executes the proposal with the provided ProposalID or rejects
52// it when there is an execution error.
53// If the proposal was denied, it will return false. If the proposal is correctly
54// executed, it will return true, unless execution fails with an error, in which case
55// proposal is rejected with the error as the reason.
56// This function allows to finish proposals by rejecting them when there is a state
57// change or an error in the proposal parameters that makes execution fail, potentially
58// leaving the proposal active forever because it can't be successfully executed.
59func ExecuteOrRejectProposal(cur realm, pid ProposalID) bool {
60 return executeProposal(cur, pid, true)
61}
62
63// CreateProposal will try to create a new proposal, that will be validated by the actual
64// govDAO implementation. If the proposal cannot be created, an error will be returned.
65func CreateProposal(cur realm, r ProposalRequest) (ProposalID, error) {
66 if dao == nil {
67 return -1, errors.New("DAO not initialized")
68 }
69 author, err := dao.PreCreateProposal(0, cur, r)
70 if err != nil {
71 return -1, err
72 }
73
74 p := &Proposal{
75 author: author,
76 title: r.title,
77 description: r.description,
78 executor: r.executor,
79 allowedDAOs: allowedDAOs[:],
80 }
81
82 pid := proposals.SetProposal(p)
83 dao.PostCreateProposal(0, cur, r, pid)
84
85 chain.Emit("ProposalCreated",
86 "id", strconv.FormatInt(int64(pid), 10),
87 )
88
89 return pid, nil
90}
91
92func MustVoteOnProposal(cur realm, r VoteRequest) {
93 if err := VoteOnProposal(cur, r); err != nil {
94 panic(err.Error())
95 }
96}
97
98// VoteOnProposal sends a vote to the actual govDAO implementation.
99// If the voter cannot vote the specified proposal, this method will return an error
100// with the explanation of why.
101func VoteOnProposal(cur realm, r VoteRequest) error {
102 if dao == nil {
103 return errors.New("DAO not initialized")
104 }
105 return dao.VoteOnProposal(0, cur, r)
106}
107
108// MustVoteOnProposalSimple is like MustVoteOnProposal but intended to be used through gnokey with basic types.
109func MustVoteOnProposalSimple(cur realm, pid int64, option string) {
110 MustVoteOnProposal(cur, VoteRequest{
111 Option: VoteOption(option),
112 ProposalID: ProposalID(pid),
113 })
114}
115
116func MustGetProposal(pid ProposalID) *Proposal {
117 p, err := GetProposal(pid)
118 if err != nil {
119 panic(err.Error())
120 }
121
122 return p
123}
124
125// GetProposal gets created proposal by its ID. Non-crossing pure read:
126// looks up the proposal in this realm's package var. Callable directly
127// from any realm without cross-call syntax.
128func GetProposal(pid ProposalID) (*Proposal, error) {
129 if dao == nil {
130 return nil, errors.New("DAO not initialized")
131 }
132 prop := proposals.GetProposal(pid)
133 if prop == nil {
134 return nil, errors.New(ufmt.Sprintf("Proposal %v does not exist.", int64(pid)))
135 }
136 return prop, nil
137}
138
139// UpdateImpl is a method intended to be used on a proposal.
140// This method will update the current govDAO implementation
141// to a new one. AllowedDAOs are a list of realms that can
142// call this method, in case the new DAO implementation had
143// a breaking bug. Any value set as nil will be ignored.
144// If AllowedDAOs field is not set correctly, the actual DAO
145// implementation wont be able to execute new Proposals!
146func UpdateImpl(cur realm, r UpdateRequest) {
147 gRealm := cur.Previous().PkgPath()
148
149 if !InAllowedDAOs(gRealm) {
150 panic("permission denied for prev realm: " + gRealm)
151 }
152
153 if r.AllowedDAOs != nil {
154 allowedDAOs = r.AllowedDAOs
155 }
156
157 if r.DAO != nil {
158 dao = r.DAO
159 }
160}
161
162func AllowedDAOs() []string {
163 dup := make([]string, len(allowedDAOs))
164 copy(dup, allowedDAOs)
165 return dup
166}
167
168func InAllowedDAOs(pkg string) bool {
169 if len(allowedDAOs) == 0 {
170 return true // corner case for initialization
171 }
172 for _, d := range allowedDAOs {
173 if pkg == d {
174 return true
175 }
176 }
177 return false
178}
179
180func executeProposal(cur realm, pid ProposalID, execErrorRejects bool) bool {
181 if dao == nil {
182 return false
183 }
184 execute, err := dao.PreExecuteProposal(0, cur, pid)
185 if err != nil {
186 panic(err.Error())
187 }
188
189 if !execute {
190 return false
191 }
192 prop, err := GetProposal(pid)
193 if err != nil {
194 panic(err.Error())
195 }
196
197 err = dao.ExecuteProposal(0, cur, pid, prop.executor)
198 if err != nil {
199 if execErrorRejects {
200 return false
201 }
202
203 panic(err.Error())
204 }
205 return true
206}