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