governance_execute.gno
7.80 Kb · 282 lines
1package v1
2
3import (
4 "chain"
5 "chain/runtime"
6 "time"
7
8 "gno.land/r/gnoswap/common"
9 "gno.land/r/gnoswap/emission"
10 "gno.land/r/gnoswap/halt"
11
12 "gno.land/r/gnoswap/gov/governance"
13)
14
15// Execute executes an approved proposal.
16//
17// Processes and implements governance decisions after successful voting.
18// Enforces timelock delays and execution windows for security.
19// Anyone can trigger execution to ensure decentralization.
20//
21// Parameters:
22// - proposalID: ID of the proposal to execute
23//
24// Requirements:
25// - Proposal must have passed (majority yes votes)
26// - Quorum must be reached (50% of xGNS supply)
27// - Timelock period must have elapsed (1 day default)
28// - Must be within execution window (30 days default)
29// - Proposal not already executed or cancelled
30//
31// Effects:
32// - Executes proposal actions (parameter changes, treasury transfers)
33// - Marks proposal as executed
34// - Emits execution event
35// - Refunds gas costs from treasury
36//
37// Returns executed proposal ID.
38// Callable by anyone once proposal is executable.
39func (gv *governanceV1) Execute(_ int, rlm realm, proposalID int64) int64 {
40 if !rlm.IsCurrent() {
41 panic(errSpoofedRealm)
42 }
43
44 // Check if execution is allowed (system not halted for execution)
45 halt.AssertIsNotHaltedGovernance()
46
47 // Get caller information and current blockchain state
48 prev := rlm.Previous()
49 caller := prev.Address()
50 currentHeight := runtime.ChainHeight()
51 currentAt := time.Now().Unix()
52
53 // Mint and distribute GNS tokens as part of the execution process
54 emission.MintAndDistributeGns(cross(rlm))
55
56 // Attempt to execute the proposal with current context
57 proposal, err := gv.executeProposal(
58 0, rlm,
59 proposalID,
60 currentAt,
61 currentHeight,
62 caller,
63 )
64 if err != nil {
65 panic(err)
66 }
67
68 // Emit execution event for tracking and auditing
69 chain.Emit(
70 "Execute",
71 "prevAddr", prev.Address().String(),
72 "prevRealm", prev.PkgPath(),
73 "proposalId", formatInt(proposalID),
74 )
75
76 return proposal.ID()
77}
78
79// executeProposal handles core logic of proposal execution.
80func (gv *governanceV1) executeProposal(
81 _ int, rlm realm,
82 proposalID int64,
83 executedAt int64,
84 executedHeight int64,
85 executedBy address,
86) (*governance.Proposal, error) {
87 // Retrieve the proposal from storage
88 proposal, ok := gv.getProposal(proposalID)
89 if !ok {
90 return nil, errDataNotFound
91 }
92
93 // Text proposals cannot be executed (they are informational only)
94 if proposal.IsTextType() {
95 return nil, errTextProposalNotExecutable
96 }
97
98 proposalResolver := NewProposalResolver(proposal)
99
100 // Verify proposal is in executable state (timing and voting requirements met)
101 if !proposalResolver.IsExecutable(executedAt) {
102 return nil, errProposalNotExecutable
103 }
104
105 // Mark proposal as executed in its status
106 err := proposalResolver.execute(executedAt, executedHeight, executedBy)
107 if err != nil {
108 return nil, err
109 }
110
111 // Execute proposal based on its type
112 switch proposal.Type() {
113 case governance.CommunityPoolSpend:
114 // Execute community pool spending (token transfers)
115 err = executeCommunityPoolSpend(0, rlm, proposal, globalParameterRegistry, executedAt, executedHeight, executedBy)
116 if err != nil {
117 return nil, err
118 }
119 case governance.ParameterChange:
120 // Execute parameter changes (governance configuration updates)
121 err = executeParameterChange(0, rlm, proposal, globalParameterRegistry, executedAt, executedHeight, executedBy)
122 if err != nil {
123 return nil, err
124 }
125 }
126
127 return proposal, nil
128}
129
130// Cancel cancels a proposal in upcoming status.
131//
132// Allows proposers to withdraw their proposals before voting begins.
133// Prevents accidental or malicious proposals from reaching vote.
134// Safety mechanism for proposal errors or changed circumstances.
135//
136// Parameters:
137// - proposalID: ID of the proposal to cancel
138//
139// Requirements:
140// - Must be called by original proposer
141// - Proposal must be in "upcoming" status
142// - Voting must not have started yet
143// - Proposal not already cancelled or executed
144//
145// Effects:
146// - Sets proposal status to "cancelled"
147// - Prevents future voting or execution
148// - Emits cancellation event
149// - Frees up proposer's proposal slot
150//
151// Returns cancelled proposal ID.
152// Only callable by original proposer before voting begins.
153func (gv *governanceV1) Cancel(_ int, rlm realm, proposalID int64) int64 {
154 if !rlm.IsCurrent() {
155 panic(errSpoofedRealm)
156 }
157
158 halt.AssertIsNotHaltedGovernance()
159
160 prev := rlm.Previous()
161 caller := prev.Address()
162 assertCallerIsProposer(gv, proposalID, caller)
163
164 // Get current blockchain state and caller information
165 currentHeight := runtime.ChainHeight()
166 currentAt := time.Now().Unix()
167
168 // Mint and distribute GNS tokens as part of the process
169 emission.MintAndDistributeGns(cross(rlm))
170
171 // Attempt to cancel the proposal
172 proposal, err := gv.cancel(proposalID, currentAt, currentHeight, caller)
173 if err != nil {
174 panic(err)
175 }
176
177 // Emit cancellation event for tracking
178 chain.Emit(
179 "Cancel",
180 "prevAddr", prev.Address().String(),
181 "prevRealm", prev.PkgPath(),
182 "proposalId", formatInt(proposalID),
183 )
184
185 return proposal.ID()
186}
187
188// cancel handles core logic of proposal cancellation.
189// Validates proposal state and updates status to canceled.
190func (gv *governanceV1) cancel(
191 proposalID, canceledAt, canceledHeight int64,
192 canceledBy address,
193) (proposal *governance.Proposal, err error) {
194 // Retrieve the proposal from storage
195 proposal, ok := gv.getProposal(proposalID)
196 if !ok {
197 return nil, errDataNotFound
198 }
199
200 proposalResolver := NewProposalResolver(proposal)
201
202 // Attempt to cancel the proposal (this validates cancellation conditions)
203 err = proposalResolver.cancel(canceledAt, canceledHeight, canceledBy)
204 if err != nil {
205 return nil, err
206 }
207
208 return proposal, nil
209}
210
211// executeCommunityPoolSpend executes community pool spending proposals.
212// Handles token transfers from community pool to specified recipients.
213func executeCommunityPoolSpend(
214 _ int, rlm realm,
215 proposal *governance.Proposal,
216 parameterRegistry *ParameterRegistry,
217 executedAt int64,
218 executedHeight int64,
219 executedBy address,
220) error {
221 // Verify token registration for community pool spending
222 if proposal.IsCommunityPoolSpendType() {
223 common.MustRegistered(proposal.Data().CommunityPoolSpend().TokenPath())
224 }
225
226 // Execute all parameter changes defined in the proposal
227 dataResolver := NewProposalDataResolver(proposal.Data())
228 parameterChangesInfos, err := dataResolver.ParameterChangesInfos()
229 if err != nil {
230 return err
231 }
232 for _, parameterChangeInfo := range parameterChangesInfos {
233 // Get the appropriate handler for this parameter change
234 key := makeHandlerKey(parameterChangeInfo.PkgPath(), parameterChangeInfo.Function())
235 handler, err := parameterRegistry.Handler(key)
236 if err != nil {
237 return err
238 }
239
240 // Execute the parameter change with provided parameters
241 err = handler.Execute(0, rlm, parameterChangeInfo.Params())
242 if err != nil {
243 return err
244 }
245 }
246
247 return nil
248}
249
250// executeParameterChange executes parameter change proposals.
251// Handles governance configuration updates and system parameter modifications.
252func executeParameterChange(
253 _ int, rlm realm,
254 proposal *governance.Proposal,
255 parameterRegistry *ParameterRegistry,
256 executedAt int64,
257 executedHeight int64,
258 executedBy address,
259) error {
260 // Execute all parameter changes defined in the proposal
261 dataResolver := NewProposalDataResolver(proposal.Data())
262 parameterChangesInfos, err := dataResolver.ParameterChangesInfos()
263 if err != nil {
264 return err
265 }
266 for _, parameterChangeInfo := range parameterChangesInfos {
267 // Get the appropriate handler for this parameter change
268 key := makeHandlerKey(parameterChangeInfo.PkgPath(), parameterChangeInfo.Function())
269 handler, err := parameterRegistry.Handler(key)
270 if err != nil {
271 return err
272 }
273
274 // Execute the parameter change with provided parameters
275 err = handler.Execute(0, rlm, parameterChangeInfo.Params())
276 if err != nil {
277 return err
278 }
279 }
280
281 return nil
282}