Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

proposal_data.gno

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