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