Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}