Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}