governance_execute.gno
7.59 Kb ยท 264 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(proposalID int64) int64 {
40 // Check if execution is allowed (system not halted for execution)
41 halt.AssertIsNotHaltedGovernance()
42
43 // Get caller information and current blockchain state
44 caller := runtime.PreviousRealm().Address()
45 currentHeight := runtime.ChainHeight()
46 currentAt := time.Now().Unix()
47
48 // Mint and distribute GNS tokens as part of the execution process
49 emission.MintAndDistributeGns(cross)
50
51 // Attempt to execute the proposal with current context
52 proposal, err := gv.executeProposal(
53 proposalID,
54 currentAt,
55 currentHeight,
56 caller,
57 )
58 if err != nil {
59 panic(err)
60 }
61
62 // Emit execution event for tracking and auditing
63 previousRealm := runtime.PreviousRealm()
64 chain.Emit(
65 "Execute",
66 "prevAddr", previousRealm.Address().String(),
67 "prevRealm", previousRealm.PkgPath(),
68 "proposalId", formatInt(proposalID),
69 )
70
71 return proposal.ID()
72}
73
74// executeProposal handles core logic of proposal execution.
75func (gv *governanceV1) executeProposal(
76 proposalID int64,
77 executedAt int64,
78 executedHeight int64,
79 executedBy address,
80) (*governance.Proposal, error) {
81 // Retrieve the proposal from storage
82 proposal, ok := gv.getProposal(proposalID)
83 if !ok {
84 return nil, errDataNotFound
85 }
86
87 // Text proposals cannot be executed (they are informational only)
88 if proposal.IsTextType() {
89 return nil, errTextProposalNotExecutable
90 }
91
92 proposalResolver := NewProposalResolver(proposal)
93
94 // Verify proposal is in executable state (timing and voting requirements met)
95 if !proposalResolver.IsExecutable(executedAt) {
96 return nil, errProposalNotExecutable
97 }
98
99 // Mark proposal as executed in its status
100 err := proposalResolver.execute(executedAt, executedHeight, executedBy)
101 if err != nil {
102 return nil, err
103 }
104
105 // Execute proposal based on its type
106 switch proposal.Type() {
107 case governance.CommunityPoolSpend:
108 // Execute community pool spending (token transfers)
109 err = executeCommunityPoolSpend(proposal, globalParameterRegistry, executedAt, executedHeight, executedBy)
110 if err != nil {
111 return nil, err
112 }
113 case governance.ParameterChange:
114 // Execute parameter changes (governance configuration updates)
115 err = executeParameterChange(proposal, globalParameterRegistry, executedAt, executedHeight, executedBy)
116 if err != nil {
117 return nil, err
118 }
119 }
120
121 return proposal, nil
122}
123
124// Cancel cancels a proposal in upcoming status.
125//
126// Allows proposers to withdraw their proposals before voting begins.
127// Prevents accidental or malicious proposals from reaching vote.
128// Safety mechanism for proposal errors or changed circumstances.
129//
130// Parameters:
131// - proposalID: ID of the proposal to cancel
132//
133// Requirements:
134// - Must be called by original proposer
135// - Proposal must be in "upcoming" status
136// - Voting must not have started yet
137// - Proposal not already cancelled or executed
138//
139// Effects:
140// - Sets proposal status to "cancelled"
141// - Prevents future voting or execution
142// - Emits cancellation event
143// - Frees up proposer's proposal slot
144//
145// Returns cancelled proposal ID.
146// Only callable by original proposer before voting begins.
147func (gv *governanceV1) Cancel(proposalID int64) int64 {
148 halt.AssertIsNotHaltedGovernance()
149
150 caller := runtime.PreviousRealm().Address()
151 assertCallerIsProposer(gv, proposalID, caller)
152
153 // Get current blockchain state and caller information
154 currentHeight := runtime.ChainHeight()
155 currentAt := time.Now().Unix()
156
157 // Mint and distribute GNS tokens as part of the process
158 emission.MintAndDistributeGns(cross)
159
160 // Attempt to cancel the proposal
161 proposal, err := gv.cancel(proposalID, currentAt, currentHeight, caller)
162 if err != nil {
163 panic(err)
164 }
165
166 // Emit cancellation event for tracking
167 previousRealm := runtime.PreviousRealm()
168 chain.Emit(
169 "Cancel",
170 "prevAddr", previousRealm.Address().String(),
171 "prevRealm", previousRealm.PkgPath(),
172 "proposalId", formatInt(proposalID),
173 )
174
175 return proposal.ID()
176}
177
178// cancel handles core logic of proposal cancellation.
179// Validates proposal state and updates status to canceled.
180func (gv *governanceV1) cancel(
181 proposalID, canceledAt, canceledHeight int64,
182 canceledBy address,
183) (proposal *governance.Proposal, err error) {
184 // Retrieve the proposal from storage
185 proposal, ok := gv.getProposal(proposalID)
186 if !ok {
187 return nil, errDataNotFound
188 }
189
190 proposalResolver := NewProposalResolver(proposal)
191
192 // Attempt to cancel the proposal (this validates cancellation conditions)
193 err = proposalResolver.cancel(canceledAt, canceledHeight, canceledBy)
194 if err != nil {
195 return nil, err
196 }
197
198 return proposal, nil
199}
200
201// executeCommunityPoolSpend executes community pool spending proposals.
202// Handles token transfers from community pool to specified recipients.
203func executeCommunityPoolSpend(
204 proposal *governance.Proposal,
205 parameterRegistry *ParameterRegistry,
206 executedAt int64,
207 executedHeight int64,
208 executedBy address,
209) error {
210 // Verify token registration for community pool spending
211 if proposal.IsCommunityPoolSpendType() {
212 common.MustRegistered(proposal.Data().CommunityPoolSpend().TokenPath())
213 }
214
215 // Execute all parameter changes defined in the proposal
216 dataResolver := NewProposalDataResolver(proposal.Data())
217 parameterChangesInfos := dataResolver.ParameterChangesInfos()
218 for _, parameterChangeInfo := range parameterChangesInfos {
219 // Get the appropriate handler for this parameter change
220 key := makeHandlerKey(parameterChangeInfo.PkgPath(), parameterChangeInfo.Function())
221 handler, err := parameterRegistry.Handler(key)
222 if err != nil {
223 return err
224 }
225
226 // Execute the parameter change with provided parameters
227 err = handler.Execute(parameterChangeInfo.Params())
228 if err != nil {
229 return err
230 }
231 }
232
233 return nil
234}
235
236// executeParameterChange executes parameter change proposals.
237// Handles governance configuration updates and system parameter modifications.
238func executeParameterChange(
239 proposal *governance.Proposal,
240 parameterRegistry *ParameterRegistry,
241 executedAt int64,
242 executedHeight int64,
243 executedBy address,
244) error {
245 // Execute all parameter changes defined in the proposal
246 dataResolver := NewProposalDataResolver(proposal.Data())
247 parameterChangesInfos := dataResolver.ParameterChangesInfos()
248 for _, parameterChangeInfo := range parameterChangesInfos {
249 // Get the appropriate handler for this parameter change
250 key := makeHandlerKey(parameterChangeInfo.PkgPath(), parameterChangeInfo.Function())
251 handler, err := parameterRegistry.Handler(key)
252 if err != nil {
253 return err
254 }
255
256 // Execute the parameter change with provided parameters
257 err = handler.Execute(parameterChangeInfo.Params())
258 if err != nil {
259 return err
260 }
261 }
262
263 return nil
264}