Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

governance_execute.gno

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