proposal.gno
4.60 Kb · 147 lines
1package governance
2
3import (
4 "errors"
5 "gno.land/r/gnoswap/gov/governance"
6)
7
8type ProposalResolver struct {
9 *governance.Proposal
10 statusResolver *ProposalStatusResolver
11 dataResolver *ProposalDataResolver
12 metadataResolver *ProposalMetadataResolver
13}
14
15func NewProposalResolver(proposal *governance.Proposal) *ProposalResolver {
16 statusResolver := NewProposalStatusResolver(proposal.Status())
17 dataResolver := NewProposalDataResolver(proposal.Data())
18 metadataResolver := NewProposalMetadataResolver(proposal.Metadata())
19 return &ProposalResolver{
20 Proposal: proposal,
21 statusResolver: statusResolver,
22 dataResolver: dataResolver,
23 metadataResolver: metadataResolver,
24 }
25}
26
27// VotingTotalWeight returns total weight of all votes cast.
28func (r *ProposalResolver) VotingTotalWeight() int64 {
29 return r.statusResolver.TotalVoteWeight()
30}
31
32// IsActive determines if the proposal is currently active (can be voted on or executed).
33// A proposal is considered active if it's not rejected, expired, executed, or canceled.
34func (r *ProposalResolver) IsActive(current int64) bool {
35 // Calculate status once and reuse to avoid redundant computations
36 status := r.statusResolver.StatusType(current)
37
38 switch status {
39 case governance.StatusRejected,
40 governance.StatusExpired,
41 governance.StatusExecuted,
42 governance.StatusCanceled:
43 return false
44 case governance.StatusPassed:
45 // Text proposals become inactive once they pass (no execution needed)
46 return !r.IsTextType()
47 default:
48 // StatusUpcoming, StatusActive, StatusExecutable are considered active
49 return true
50 }
51}
52
53// Validate performs comprehensive validation of the proposal data and metadata.
54// This ensures all proposal components meet requirements before storage.
55func (r *ProposalResolver) Validate() error {
56 // Validate type-specific proposal data
57 if err := r.dataResolver.Validate(); err != nil {
58 return err
59 }
60
61 // Validate proposal metadata (title and description)
62 if err := r.metadataResolver.Validate(); err != nil {
63 return err
64 }
65
66 return nil
67}
68
69// Status returns the current status string of the proposal at the given time.
70func (r *ProposalResolver) Status(current int64) string {
71 return r.statusResolver.StatusType(current).String()
72}
73
74// StatusType returns the current status type of the proposal at the given time.
75func (r *ProposalResolver) StatusType(current int64) governance.ProposalStatusType {
76 return r.statusResolver.StatusType(current)
77}
78
79// IsVotingPeriod checks if the proposal is currently in its voting period.
80func (r *ProposalResolver) IsVotingPeriod(votedAt int64) bool {
81 return r.StatusType(votedAt) == governance.StatusActive
82}
83
84// IsExecutable determines if the proposal can be executed at the given time.
85// Only executable proposal types that have passed voting can be executed.
86func (r *ProposalResolver) IsExecutable(current int64) bool {
87 // Only certain proposal types can be executed
88 if !r.dataResolver.ProposalType().IsExecutable() {
89 return false
90 }
91
92 return r.statusResolver.IsExecutable(current)
93}
94
95// CommunityPoolSpendTokenPath returns the token path for community pool spend proposals.
96// Returns empty string for other proposal types.
97func (r *ProposalResolver) CommunityPoolSpendTokenPath() string {
98 if r.Data() == nil {
99 return ""
100 }
101
102 communityPoolSpend := r.dataResolver.CommunityPoolSpend()
103 if communityPoolSpend == nil {
104 return ""
105 }
106
107 return communityPoolSpend.TokenPath()
108}
109
110// vote records a vote for this proposal and updates vote tallies.
111// This is an internal method called during voting process.
112func (r *ProposalResolver) Vote(votedYes bool, weight int64) error {
113 return r.statusResolver.vote(votedYes, weight)
114}
115
116// execute marks the proposal as executed and records execution details.
117// This method validates execution conditions before proceeding.
118func (r *ProposalResolver) execute(
119 executedAt, executedHeight int64,
120 executedBy address,
121) error {
122 // Verify proposal is in executable state
123 if !r.IsExecutable(executedAt) {
124 return errors.New(errProposalNotExecutable)
125 }
126
127 // Mark proposal as executed
128 return r.statusResolver.execute(executedAt, executedHeight, executedBy)
129}
130
131// cancel marks the proposal as canceled and records cancellation details.
132// This method validates cancellation conditions before proceeding.
133func (r *ProposalResolver) cancel(
134 canceledAt, canceledHeight int64,
135 canceledBy address,
136) error {
137 if r.statusResolver.IsCanceled(canceledAt) {
138 return errors.New(errAlreadyCanceledProposal)
139 }
140
141 if !r.statusResolver.IsUpcoming(canceledAt) {
142 return errors.New(errUnableToCancelVotingProposal)
143 }
144
145 // Mark proposal as canceled
146 return r.statusResolver.cancel(canceledAt, canceledHeight, canceledBy)
147}