Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}