Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}