proposal_data.gno
7.96 Kb · 273 lines
1package governance
2
3import (
4 "strings"
5
6 "gno.land/p/gnoswap/utils"
7 ufmt "gno.land/p/nt/ufmt/v0"
8
9 "gno.land/r/gnoswap/gov/governance"
10)
11
12type ProposalMetadataResolver struct {
13 *governance.ProposalMetadata
14}
15
16func NewProposalMetadataResolver(metadata *governance.ProposalMetadata) *ProposalMetadataResolver {
17 return &ProposalMetadataResolver{
18 ProposalMetadata: metadata,
19 }
20}
21
22// Validate performs comprehensive validation of the proposal metadata.
23// Checks title and description length and content requirements.
24//
25// Returns:
26// - error: validation error if metadata is invalid
27func (r *ProposalMetadataResolver) Validate() error {
28 // Validate title meets requirements
29 if err := r.validateTitle(r.Title()); err != nil {
30 return err
31 }
32
33 // Validate description meets requirements
34 if err := r.validateDescription(r.Description()); err != nil {
35 return err
36 }
37
38 return nil
39}
40
41// validateTitle checks if the proposal title meets length and content requirements.
42//
43// Parameters:
44// - title: title string to validate
45//
46// Returns:
47// - error: validation error if title is invalid
48func (r *ProposalMetadataResolver) validateTitle(title string) error {
49 // Title cannot be empty
50 if title == "" {
51 return makeErrorWithDetails(errInvalidInput, "title is empty")
52 }
53
54 // Title cannot exceed maximum length
55 if len(title) > maxTitleLength {
56 return makeErrorWithDetails(
57 errInvalidInput,
58 "title is too long, max length is 255 characters",
59 )
60 }
61
62 return nil
63}
64
65// validateDescription checks if the proposal description meets length and content requirements.
66//
67// Parameters:
68// - description: description string to validate
69//
70// Returns:
71// - error: validation error if description is invalid
72func (r *ProposalMetadataResolver) validateDescription(description string) error {
73 // Description cannot be empty
74 if description == "" {
75 return makeErrorWithDetails(
76 errInvalidInput,
77 "description is empty",
78 )
79 }
80
81 // Description cannot exceed maximum length
82 if len(description) > maxDescriptionLength {
83 return makeErrorWithDetails(
84 errInvalidInput,
85 "description is too long, max length is 10,000 characters",
86 )
87 }
88
89 return nil
90}
91
92// ProposalDataResolver handles business logic for proposal data.
93type ProposalDataResolver struct {
94 *governance.ProposalData
95}
96
97func NewProposalDataResolver(proposalData *governance.ProposalData) *ProposalDataResolver {
98 return &ProposalDataResolver{
99 ProposalData: proposalData,
100 }
101}
102
103// Validate performs type-specific validation of the proposal data.
104// Different proposal types have different validation requirements.
105//
106// Returns:
107// - error: validation error if data is invalid
108func (r *ProposalDataResolver) Validate() error {
109 switch r.ProposalType() {
110 case governance.Text:
111 return r.validateText()
112 case governance.CommunityPoolSpend:
113 return r.validateCommunityPoolSpend()
114 case governance.ParameterChange:
115 return r.validateParameterChange()
116 }
117 return nil
118}
119
120// validateText validates text proposal data.
121// Text proposals have no additional validation requirements.
122//
123// Returns:
124// - error: always nil for text proposals
125func (r *ProposalDataResolver) validateText() error {
126 return nil
127}
128
129// validateCommunityPoolSpend validates community pool spend proposal data.
130// Checks recipient address, token path, and amount validity.
131//
132// Returns:
133// - error: validation error if community pool spend data is invalid
134func (r *ProposalDataResolver) validateCommunityPoolSpend() error {
135 // Validate recipient address
136 communityPoolSpend := r.ProposalData.CommunityPoolSpend()
137 if communityPoolSpend == nil {
138 return makeErrorWithDetails(
139 errInvalidInput, "community pool spend info is missing")
140 }
141
142 if !communityPoolSpend.To().IsValid() {
143 return makeErrorWithDetails(
144 errInvalidInput, "to is invalid address")
145 }
146
147 // Validate amount is greater than 0
148 if communityPoolSpend.Amount() <= 0 {
149 return makeErrorWithDetails(
150 errInvalidInput, "amount is not positive")
151 }
152
153 return nil
154}
155
156// validateParameterChange validates parameter change proposal data.
157// Delegates to validateExecutions for full validation including count checks,
158// message format, handler existence, and parameter type validation.
159//
160// Returns:
161// - error: validation error if parameter change data is invalid
162func (r *ProposalDataResolver) validateParameterChange() error {
163 execution := r.Execution()
164 if execution == nil {
165 return makeErrorWithDetails(
166 errInvalidInput,
167 "execution info is missing",
168 )
169 }
170 return validateExecutions(execution.Num(), execution.Msgs())
171}
172
173// ParameterChangesInfos parses the execution messages and returns structured parameter change information.
174// Each message is expected to be in format: pkgPath*EXE*function*EXE*params
175//
176// Returns:
177// - []ParameterChangeInfo: slice of parsed parameter change information
178// - error: validation error if any execution message is malformed
179func (r *ProposalDataResolver) ParameterChangesInfos() ([]governance.ParameterChangeInfo, error) {
180 infos := make([]governance.ParameterChangeInfo, 0)
181
182 // Return empty slice if no executions
183 execution := r.Execution()
184 if execution == nil || execution.Num() <= 0 {
185 return infos, nil
186 }
187
188 // Parse each execution message
189 for _, msg := range execution.Msgs() {
190 pkgPath, function, params, partCount := parseExecutionMessage(msg)
191 if partCount != 3 {
192 return nil, makeErrorWithDetails(
193 errInvalidMessageFormat,
194 ufmt.Sprintf("malformed execution message: expected 3 parts (pkgPath, function, params), got %d", partCount),
195 )
196 }
197
198 // Create parameter change info structure
199 info := governance.NewParameterChangeInfo(pkgPath, function, params)
200 infos = append(infos, info)
201 }
202
203 return infos, nil
204}
205
206// NewProposalTextData creates proposal data for a text proposal.
207// Text proposals have no additional data requirements.
208//
209// Returns:
210// - *ProposalData: proposal data configured for text proposal
211func NewProposalTextData() *governance.ProposalData {
212 return governance.NewProposalData(governance.Text, nil, nil)
213}
214
215// NewProposalCommunityPoolSpendData creates proposal data for a community pool spend proposal.
216// Automatically generates the execution message for the token transfer.
217//
218// Parameters:
219// - tokenPath: path of the token to transfer
220// - to: recipient address for the transfer
221// - amount: amount of tokens to transfer
222// - communityPoolPackagePath: package path of the community pool contract
223//
224// Returns:
225// - *ProposalData: proposal data configured for community pool spending
226func NewProposalCommunityPoolSpendData(
227 tokenPath string,
228 to address,
229 amount int64,
230 communityPoolPackagePath string,
231) *governance.ProposalData {
232 // Create execution message for the token transfer
233 executionInfoMessage := makeExecuteMessage(
234 communityPoolPackagePath,
235 "TransferToken",
236 []string{tokenPath, to.String(), utils.FormatInt(amount)},
237 )
238
239 return governance.NewProposalData(
240 governance.CommunityPoolSpend,
241 governance.NewCommunityPoolSpendInfo(to, tokenPath, amount),
242 governance.NewExecutionInfo(1, []string{executionInfoMessage}),
243 )
244}
245
246// NewProposalExecutionData creates proposal data for a parameter change proposal.
247// Each message in executions should be formatted as <pkgPath>*EXE*<function>*EXE*<params>,
248// separated by *GOV* when there are multiple messages.
249//
250// Parameters:
251// - numToExecute: number of parameter changes to execute
252// - executions: raw encoded execution string with parameter changes
253//
254// Returns:
255// - *ProposalData: proposal data configured for parameter changes
256func NewProposalExecutionData(numToExecute int64, executions string) *governance.ProposalData {
257 return governance.NewProposalData(
258 governance.ParameterChange,
259 nil,
260 governance.NewExecutionInfo(numToExecute, splitExecutionsRaw(executions)),
261 )
262}
263
264// makeExecuteMessage creates a message to execute a function.
265// Message format: <pkgPath>*EXE*<function>*EXE*<params>.
266func makeExecuteMessage(pkgPath, function string, params []string) string {
267 messageParams := []string{
268 pkgPath,
269 function,
270 strings.Join(params, ","),
271 }
272 return strings.Join(messageParams, parameterSeparator)
273}