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

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