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