Kind Realm (renderable)
Name v1
Namespace gnoswap / gov / governance
Exported functions n/a — Gnoland RPC unavailable (functions): rpc abci_query: HTTP 500
Module gno.land/r/gnoswap/gov/governance/v1
gno 0.9 governance_propose.gno gno
⧉
1 package governance 2 3 import ( 4 "chain" 5 "chain/runtime" 6 "errors" 7 "time" 8 Gnoland RPC unavailable (functions): rpc abci_query: HTTP 500
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
This realm does not provide renderable output (vm/qrender → NoRenderDeclError).
View on gnoweb ↗ vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
9 "gno.land/r/gnoswap/access/v1"
10
11 gnsmath "gno.land/p/gnoswap/gnsmath/v1"
12 "gno.land/p/gnoswap/utils/v1"
13
14 "gno.land/r/gnoswap/gov/xgns"
15 "gno.land/r/gnoswap/halt/v1"
16
17 "gno.land/r/gnoswap/gov/governance"
18 )
19
20 // ProposeText creates a text proposal for community discussion.
21 //
22 // Signal proposals for non-binding community sentiment.
23 // Used for policy discussions, roadmap planning, and community feedback.
24 // No on-chain execution, serves as formal governance record.
25 //
26 // Parameters:
27 //
28 // - _: Internal call discriminator; pass 0.
29 //
30 // - rlm: Propagated realm context; validated as current before proposal state writes.
31 //
32 // - title: Short, descriptive proposal title (up to 255 characters; 100 is a UX recommendation)
33 //
34 // - description: Full proposal content with rationale and context
35 //
36 // Requirements:
37 // - Caller must hold at least the configured ProposalCreationThreshold amount in xGNS
38 // - No other active proposal from same address
39 // - Title and description must be non-empty
40 //
41 // Process:
42 // - Configured delay before voting starts (1 day by default)
43 // - Configured voting period (7 days by default)
44 // - Quorum and a strict yes-over-no majority determine the outcome
45 // - No execution phase (signal only)
46 //
47 // Returns:
48 // - newProposalId: ID of the newly created text proposal
49 func (gv *governanceV1) ProposeText(
50 _ int , rlm realm,
51 title string ,
52 description string ,
53 ) (newProposalId int64 ) {
54 access.AssertIsRlmCurrent( 0 , rlm)
55
56 halt.AssertIsNotHaltedGovernance()
57
58 prev := rlm.Previous()
59 callerAddress := prev.Address()
60
61 createdAt := time.Now().Unix()
62 createdHeight := runtime.ChainHeight()
63 xgnsBalance := xgns.BalanceOf(callerAddress)
64
65 config, ok := gv.getCurrentConfig()
66 if !ok {
67 panic(errors.New(errDataNotFound))
68 }
69
70 // Clean up inactive user proposals before checking if caller already has an active proposal
71 err := gv.removeInactiveUserProposals( 0 , rlm, callerAddress, createdAt)
72 if err != nil {
73 panic(err)
74 }
75
76 // Check if caller already has an active proposal (one proposal per address)
77 if gv.hasActiveProposal(callerAddress) {
78 panic(errors.New(errAlreadyActiveProposal))
79 }
80
81 // Get timestamp anchor and total voting weight for proposal creation
82 maxVotingWeight, snapshotTime, err := gv.getVotingWeightSnapshot(
83 createdAt,
84 config.VotingWeightSmoothingDuration,
85 )
86 if err != nil {
87 panic(err)
88 }
89 quorumWeight := gv.stakerAccessor.GetTotalxGnsSupply()
90
91 // Create the text proposal with metadata
92 proposal, err := gv.createProposal(
93 0 , rlm,
94 governance.Text,
95 config,
96 maxVotingWeight,
97 quorumWeight,
98 snapshotTime,
99 governance.NewProposalMetadata(title, description),
100 NewProposalTextData(),
101 callerAddress,
102 xgnsBalance,
103 createdAt,
104 createdHeight,
105 )
106 if err != nil {
107 panic(err)
108 }
109
110 // Initialize empty voting info tree for this proposal (votes will be added as users vote)
111 err = gv.updateProposalUserVotes( 0 , rlm, proposal, governance.NewProposalUserVotingInfoTree())
112 if err != nil {
113 panic(err)
114 }
115
116 // Emit proposal creation event for indexing and tracking
117 chain.Emit(
118 "ProposeText" ,
119 "prevAddr" , prev.Address().String(),
120 "prevRealm" , prev.PkgPath(),
121 "title" , title,
122 "proposalId" , utils.FormatInt(proposal.ID()),
123 "quorumAmount" , utils.FormatInt(proposal.VotingQuorumAmount()),
124 "maxVotingWeight" , utils.FormatInt(proposal.VotingMaxWeight()),
125 "configVersion" , utils.FormatInt(proposal.ConfigVersion()),
126 "createdAt" , utils.FormatInt(proposal.CreatedAt()),
127 )
128
129 return proposal.ID()
130 }
131
132 // ProposeCommunityPoolSpend creates a treasury disbursement proposal.
133 //
134 // Allocates community pool funds for approved purposes.
135 // Supports grants, development funding, and protocol incentives.
136 // The transfer is attempted when the approved proposal executes.
137 //
138 // Parameters:
139 //
140 // - _: Internal call discriminator; pass 0.
141 //
142 // - rlm: Propagated realm context; validated as current before proposal state writes.
143 //
144 // - title: Proposal title describing purpose
145 //
146 // - description: Detailed justification and budget breakdown
147 //
148 // - to: Recipient address for funds
149 //
150 // - tokenPath: Registered token contract path (e.g., "gno.land/r/gnoswap/gns")
151 //
152 // - amount: Strictly positive amount to transfer (in smallest unit)
153 //
154 // Requirements:
155 // - Caller must hold at least the configured ProposalCreationThreshold amount in xGNS
156 // - Valid recipient address
157 // - Registered token path
158 // - Community-pool balance is checked at execution, not proposal creation
159 //
160 // Security:
161 // - Enforces timelock after approval
162 // - Single transfer per proposal
163 // - Tracks all disbursements on-chain
164 //
165 // Returns:
166 // - newProposalId: ID of the newly created community-pool-spend proposal
167 func (gv *governanceV1) ProposeCommunityPoolSpend(
168 _ int , rlm realm,
169 title string ,
170 description string ,
171 to address,
172 tokenPath string ,
173 amount int64 ,
174 ) (newProposalId int64 ) {
175 access.AssertIsRlmCurrent( 0 , rlm)
176
177 halt.AssertIsNotHaltedGovernance()
178
179 assertIsValidToken(tokenPath)
180
181 createdAt := time.Now().Unix()
182 createdHeight := runtime.ChainHeight()
183
184 prev := rlm.Previous()
185 callerAddress := prev.Address()
186 xgnsBalance := xgns.BalanceOf(callerAddress)
187
188 config, ok := gv.getCurrentConfig()
189 if !ok {
190 panic(errors.New(errDataNotFound))
191 }
192
193 // Clean up inactive user proposals before checking if caller already has an active proposal
194 err := gv.removeInactiveUserProposals( 0 , rlm, callerAddress, createdAt)
195 if err != nil {
196 panic(err)
197 }
198
199 // Check if caller already has an active proposal (one proposal per address)
200 if gv.hasActiveProposal(callerAddress) {
201 panic(errors.New(errAlreadyActiveProposal))
202 }
203
204 // Get timestamp anchor and total voting weight for proposal creation
205 maxVotingWeight, snapshotTime, err := gv.getVotingWeightSnapshot(
206 createdAt,
207 config.VotingWeightSmoothingDuration,
208 )
209 if err != nil {
210 panic(err)
211 }
212 quorumWeight := gv.stakerAccessor.GetTotalxGnsSupply()
213
214 // Create the community pool spend proposal with execution data
215 proposal, err := gv.createProposal(
216 0 , rlm,
217 governance.CommunityPoolSpend,
218 config,
219 maxVotingWeight,
220 quorumWeight,
221 snapshotTime,
222 governance.NewProposalMetadata(title, description),
223 NewProposalCommunityPoolSpendData(tokenPath, to, amount, COMMUNITY_POOL_PATH),
224 callerAddress,
225 xgnsBalance,
226 createdAt,
227 createdHeight,
228 )
229 if err != nil {
230 panic(err)
231 }
232
233 // Initialize empty voting info tree for this proposal (votes will be added as users vote)
234 err = gv.updateProposalUserVotes( 0 , rlm, proposal, governance.NewProposalUserVotingInfoTree())
235 if err != nil {
236 panic(err)
237 }
238
239 // Emit proposal creation event for indexing and tracking
240 chain.Emit(
241 "ProposeCommunityPoolSpend" ,
242 "prevAddr" , prev.Address().String(),
243 "prevRealm" , prev.PkgPath(),
244 "title" , title,
245 "to" , to.String(),
246 "tokenPath" , tokenPath,
247 "amount" , utils.FormatInt(amount),
248 "proposalId" , utils.FormatInt(proposal.ID()),
249 "quorumAmount" , utils.FormatInt(proposal.VotingQuorumAmount()),
250 "maxVotingWeight" , utils.FormatInt(proposal.VotingMaxWeight()),
251 "configVersion" , utils.FormatInt(proposal.ConfigVersion()),
252 "createdAt" , utils.FormatInt(proposal.CreatedAt()),
253 )
254
255 return proposal.ID()
256 }
257
258 // ProposeParameterChange creates a protocol parameter update proposal.
259 //
260 // Modifies system parameters through governance.
261 // Supports multiple parameter changes in single proposal.
262 // Changes apply atomically on execution.
263 //
264 // Parameters:
265 //
266 // - _: Internal call discriminator; pass 0.
267 //
268 // - rlm: Propagated realm context; validated as current before proposal state writes.
269 //
270 // - title: Clear description of changes
271 //
272 // - description: Rationale and impact analysis
273 //
274 // - numToExecute: Number of parameter changes
275 //
276 // - executions: Raw execution string encoded as messages separated by *GOV*.
277 // Each message is formatted as <pkgPath>*EXE*<function>*EXE*<params>.
278 //
279 // Example execution format (one registered handler; all seven arguments are required):
280 //
281 // gno.land/r/gnoswap/gov/governance*EXE*Reconfigure*EXE*86400,604800,86400,50,1000000000,86400,2592000
282 //
283 // Requirements:
284 // - Caller must hold at least the configured ProposalCreationThreshold amount in xGNS
285 // - Encoded execution messages must match numToExecute
286 // - Target handlers must exist in the parameter registry
287 // - Parameters must match registered function signatures
288 //
289 // Returns:
290 // - newProposalId: ID of the newly created parameter-change proposal
291 func (gv *governanceV1) ProposeParameterChange(
292 _ int , rlm realm,
293 title string ,
294 description string ,
295 numToExecute int64 ,
296 executions string ,
297 ) (newProposalId int64 ) {
298 access.AssertIsRlmCurrent( 0 , rlm)
299
300 proposalData := NewProposalExecutionData(numToExecute, executions)
301 assertIsNotHaltedGovernanceForProposalData(proposalData)
302
303 prev := rlm.Previous()
304 callerAddress := prev.Address()
305
306 createdAt := time.Now().Unix()
307 createdHeight := runtime.ChainHeight()
308 xgnsBalance := xgns.BalanceOf(callerAddress)
309
310 config, ok := gv.getCurrentConfig()
311 if !ok {
312 panic(errors.New(errDataNotFound))
313 }
314
315 // Clean up inactive user proposals before checking if caller already has an active proposal
316 err := gv.removeInactiveUserProposals( 0 , rlm, callerAddress, createdAt)
317 if err != nil {
318 panic(err)
319 }
320
321 // Check if caller already has an active proposal (one proposal per address)
322 if gv.hasActiveProposal(callerAddress) {
323 panic(errors.New(errAlreadyActiveProposal))
324 }
325
326 // Get timestamp anchor and total voting weight for proposal creation
327 maxVotingWeight, snapshotTime, err := gv.getVotingWeightSnapshot(
328 createdAt,
329 config.VotingWeightSmoothingDuration,
330 )
331 if err != nil {
332 panic(err)
333 }
334 quorumWeight := gv.stakerAccessor.GetTotalxGnsSupply()
335
336 // Create the parameter change proposal with execution data
337 proposal, err := gv.createProposal(
338 0 , rlm,
339 governance.ParameterChange,
340 config,
341 maxVotingWeight,
342 quorumWeight,
343 snapshotTime,
344 governance.NewProposalMetadata(title, description),
345 proposalData,
346 callerAddress,
347 xgnsBalance,
348 createdAt,
349 createdHeight,
350 )
351 if err != nil {
352 panic(err)
353 }
354
355 // Initialize empty voting info tree for this proposal (votes will be added as users vote)
356 err = gv.updateProposalUserVotes( 0 , rlm, proposal, governance.NewProposalUserVotingInfoTree())
357 if err != nil {
358 panic(err)
359 }
360
361 // Emit proposal creation event for indexing and tracking
362 chain.Emit(
363 "ProposeParameterChange" ,
364 "prevAddr" , prev.Address().String(),
365 "prevRealm" , prev.PkgPath(),
366 "title" , title,
367 "numToExecute" , utils.FormatInt(numToExecute),
368 "proposalId" , utils.FormatInt(proposal.ID()),
369 "quorumAmount" , utils.FormatInt(proposal.VotingQuorumAmount()),
370 "maxVotingWeight" , utils.FormatInt(proposal.VotingMaxWeight()),
371 "configVersion" , utils.FormatInt(proposal.ConfigVersion()),
372 "createdAt" , utils.FormatInt(proposal.CreatedAt()),
373 )
374
375 return proposal.ID()
376 }
377
378 // createProposal handles proposal creation logic.
379 // Validates input data, checks proposer eligibility, and creates proposal object.
380 func (gv *governanceV1) createProposal(
381 _ int , rlm realm,
382 proposalType governance.ProposalType,
383 config governance.Config,
384 maxVotingWeight int64 ,
385 quorumWeight int64 ,
386 snapshotTime int64 ,
387 proposalMetadata *governance.ProposalMetadata,
388 proposalData *governance.ProposalData,
389 proposerAddress address,
390 proposerXGnsBalance int64 ,
391 createdAt int64 ,
392 createdHeight int64 ,
393 ) (*governance.Proposal, error ) {
394 // Validate proposal metadata (title and description)
395 metadataResolver := NewProposalMetadataResolver(proposalMetadata)
396 err := metadataResolver.Validate()
397 if err != nil {
398 return nil , err
399 }
400
401 // Validate proposal data (type-specific validation)
402 dataResolver := NewProposalDataResolver(proposalData)
403 err = dataResolver.Validate()
404 if err != nil {
405 return nil , err
406 }
407
408 // Check if proposer has enough xGNS balance to create proposal
409 if proposerXGnsBalance < config.ProposalCreationThreshold {
410 return nil , errors.New(errNotEnoughBalance)
411 }
412
413 // Generate unique proposal ID
414 proposalID := gv.nextProposalID( 0 , rlm)
415
416 // Create proposal status with voting schedule and requirements
417 proposalStatus := NewProposalStatus(
418 config,
419 maxVotingWeight,
420 proposalType.IsExecutable(),
421 createdAt,
422 quorumWeight,
423 )
424
425 // Get current configuration version for tracking
426 configVersion := gv.getCurrentConfigVersion()
427
428 // Create the proposal object with a timestamp anchor for lazy voting-weight lookup
429 proposal := governance.NewProposal(
430 proposalID,
431 proposalStatus,
432 proposalMetadata,
433 proposalData,
434 proposerAddress,
435 configVersion,
436 snapshotTime,
437 createdHeight,
438 )
439
440 // Store the proposal in state
441 success := gv.addProposal( 0 , rlm, proposal)
442 if !success {
443 return nil , errors.New(errDataNotFound)
444 }
445
446 return proposal, nil
447 }
448
449 // getVotingWeightSnapshot retrieves the averaged total voting weight for proposal creation.
450 // It uses two snapshots (current and current - smoothingPeriod) and averages them.
451 func (gv *governanceV1) getVotingWeightSnapshot(
452 current,
453 smoothingPeriod int64 ,
454 ) ( int64 , int64 , error ) {
455 // Calculate the earlier timestamp by subtracting the smoothing duration
456 snapshotTime := current - smoothingPeriod
457 if snapshotTime < 0 {
458 snapshotTime = 0
459 }
460
461 // Get total delegation at the earlier timestamp from staker history
462 totalAtSnapshot, ok := gv.stakerAccessor.GetTotalDelegationAmountAtSnapshot(snapshotTime)
463 if !ok {
464 totalAtSnapshot = 0
465 }
466
467 totalAtCurrent, ok := gv.stakerAccessor.GetTotalDelegationAmountAtSnapshot(current)
468 if !ok {
469 totalAtCurrent = 0
470 }
471
472 totalVotingWeight := gnsmath.SafeAddInt64(totalAtSnapshot, totalAtCurrent) / 2
473 if totalVotingWeight <= 0 {
474 return 0 , snapshotTime, errors.New(errNotEnoughVotingWeight)
475 }
476
477 return totalVotingWeight, snapshotTime, nil
478 }
479