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

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}