Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}