proposal.gno
6.38 Kb · 205 lines
1package governance
2
3import (
4 "errors"
5
6 "gno.land/r/gnoswap/gov/governance"
7)
8
9type ProposalResolver struct {
10 *governance.Proposal
11 statusResolver *ProposalStatusResolver
12 dataResolver *ProposalDataResolver
13 metadataResolver *ProposalMetadataResolver
14}
15
16// NewProposalResolver wraps a persisted governance proposal and initializes
17// resolvers for its status, type-specific data, and metadata.
18//
19// Parameters:
20// - proposal: persisted proposal to resolve
21//
22// Returns:
23// - *ProposalResolver: resolver backed by proposal and its nested state
24func NewProposalResolver(proposal *governance.Proposal) *ProposalResolver {
25 statusResolver := NewProposalStatusResolver(proposal.Status())
26 dataResolver := NewProposalDataResolver(proposal.Data())
27 metadataResolver := NewProposalMetadataResolver(proposal.Metadata())
28 return &ProposalResolver{
29 Proposal: proposal,
30 statusResolver: statusResolver,
31 dataResolver: dataResolver,
32 metadataResolver: metadataResolver,
33 }
34}
35
36// VotingTotalWeight returns the total weight of all votes cast by combining
37// the proposal's yes and no tallies.
38//
39// Returns:
40// - int64: combined weight of all recorded votes
41func (r *ProposalResolver) VotingTotalWeight() int64 {
42 return r.statusResolver.TotalVoteWeight()
43}
44
45// IsActive determines whether the proposal is active at current.
46// Upcoming, voting, and passed executable proposals are active; rejected,
47// expired, executed, canceled, and passed text proposals are inactive.
48//
49// Parameters:
50// - current: timestamp at which the proposal status is evaluated
51//
52// Returns:
53// - bool: true when the proposal can still be voted on or executed at current
54func (r *ProposalResolver) IsActive(current int64) bool {
55 // Calculate status once and reuse to avoid redundant computations
56 status := r.statusResolver.StatusType(current)
57
58 switch status {
59 case governance.StatusRejected,
60 governance.StatusExpired,
61 governance.StatusExecuted,
62 governance.StatusCanceled:
63 return false
64 case governance.StatusPassed:
65 // Text proposals become inactive once they pass (no execution needed)
66 return !r.IsTextType()
67 default:
68 // StatusUpcoming, StatusActive, StatusExecutable are considered active
69 return true
70 }
71}
72
73// Validate performs comprehensive validation of the proposal's type-specific
74// data and metadata before the proposal is stored.
75//
76// Returns:
77// - error: nil when both data and metadata validate; the first validation error otherwise
78func (r *ProposalResolver) Validate() error {
79 // Validate type-specific proposal data
80 if err := r.dataResolver.Validate(); err != nil {
81 return err
82 }
83
84 // Validate proposal metadata (title and description)
85 if err := r.metadataResolver.Validate(); err != nil {
86 return err
87 }
88
89 return nil
90}
91
92// Status returns the current status string of the proposal at current.
93//
94// Parameters:
95// - current: timestamp at which the proposal status is evaluated
96//
97// Returns:
98// - string: human-readable status name for the proposal at current
99func (r *ProposalResolver) Status(current int64) string {
100 return r.statusResolver.StatusType(current).String()
101}
102
103// StatusType returns the current status type of the proposal at current.
104//
105// Parameters:
106// - current: timestamp at which the proposal status is evaluated
107//
108// Returns:
109// - governance.ProposalStatusType: lifecycle status at current
110func (r *ProposalResolver) StatusType(current int64) governance.ProposalStatusType {
111 return r.statusResolver.StatusType(current)
112}
113
114// IsVotingPeriod reports whether votedAt falls within the proposal's active
115// voting status.
116//
117// Parameters:
118// - votedAt: timestamp at which voting eligibility is checked
119//
120// Returns:
121// - bool: true when the proposal status at votedAt is StatusActive
122func (r *ProposalResolver) IsVotingPeriod(votedAt int64) bool {
123 return r.StatusType(votedAt) == governance.StatusActive
124}
125
126// IsExecutable determines whether the proposal can be executed at current.
127// The proposal type must support execution and its status must be executable.
128//
129// Parameters:
130// - current: timestamp at which execution eligibility is evaluated
131//
132// Returns:
133// - bool: true when this type and current lifecycle state permit execution
134func (r *ProposalResolver) IsExecutable(current int64) bool {
135 // Only certain proposal types can be executed
136 if !r.dataResolver.ProposalType().IsExecutable() {
137 return false
138 }
139
140 return r.statusResolver.IsExecutable(current)
141}
142
143// CommunityPoolSpendTokenPath returns the token path for a community pool spend proposal.
144// It returns an empty string when the proposal has no community-pool spend data.
145//
146// Returns:
147// - string: token package path to spend, or empty string for other proposal types
148func (r *ProposalResolver) CommunityPoolSpendTokenPath() string {
149 if r.Data() == nil {
150 return ""
151 }
152
153 communityPoolSpend := r.dataResolver.CommunityPoolSpend()
154 if communityPoolSpend == nil {
155 return ""
156 }
157
158 return communityPoolSpend.TokenPath()
159}
160
161// Vote records a vote for this proposal and updates its yes or no tally.
162// This is an internal method called during the voting process.
163//
164// Parameters:
165// - votedYes: true to add weight to the yes tally; false to add it to no
166// - weight: voting weight to add to the selected tally
167//
168// Returns:
169// - error: nil when the selected tally is updated; an error from vote-status validation otherwise
170func (r *ProposalResolver) Vote(votedYes bool, weight int64) error {
171 return r.statusResolver.vote(votedYes, weight)
172}
173
174// execute marks the proposal as executed and records execution details.
175// This method validates execution conditions before proceeding.
176func (r *ProposalResolver) execute(
177 executedAt, executedHeight int64,
178 executedBy address,
179) error {
180 // Verify proposal is in executable state
181 if !r.IsExecutable(executedAt) {
182 return errors.New(errProposalNotExecutable)
183 }
184
185 // Mark proposal as executed
186 return r.statusResolver.execute(executedAt, executedHeight, executedBy)
187}
188
189// cancel marks the proposal as canceled and records cancellation details.
190// This method validates cancellation conditions before proceeding.
191func (r *ProposalResolver) cancel(
192 canceledAt, canceledHeight int64,
193 canceledBy address,
194) error {
195 if r.statusResolver.IsCanceled(canceledAt) {
196 return errors.New(errAlreadyCanceledProposal)
197 }
198
199 if !r.statusResolver.IsUpcoming(canceledAt) {
200 return errors.New(errUnableToCancelVotingProposal)
201 }
202
203 // Mark proposal as canceled
204 return r.statusResolver.cancel(canceledAt, canceledHeight, canceledBy)
205}