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