github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/types/v1beta1/proposal.go (about)

     1  package v1beta1
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/cosmos/gogoproto/proto"
     9  
    10  	errorsmod "cosmossdk.io/errors"
    11  
    12  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    13  	sdk "github.com/cosmos/cosmos-sdk/types"
    14  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    15  	"github.com/cosmos/cosmos-sdk/x/gov/types"
    16  )
    17  
    18  // DefaultStartingProposalID is 1
    19  const DefaultStartingProposalID uint64 = 1
    20  
    21  // NewProposal creates a new Proposal instance
    22  func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) (Proposal, error) {
    23  	msg, ok := content.(proto.Message)
    24  	if !ok {
    25  		return Proposal{}, fmt.Errorf("%T does not implement proto.Message", content)
    26  	}
    27  
    28  	any, err := codectypes.NewAnyWithValue(msg)
    29  	if err != nil {
    30  		return Proposal{}, err
    31  	}
    32  
    33  	p := Proposal{
    34  		Content:          any,
    35  		ProposalId:       id,
    36  		Status:           StatusDepositPeriod,
    37  		FinalTallyResult: EmptyTallyResult(),
    38  		TotalDeposit:     sdk.NewCoins(),
    39  		SubmitTime:       submitTime,
    40  		DepositEndTime:   depositEndTime,
    41  	}
    42  
    43  	return p, nil
    44  }
    45  
    46  // GetContent returns the proposal Content
    47  func (p Proposal) GetContent() Content {
    48  	content, ok := p.Content.GetCachedValue().(Content)
    49  	if !ok {
    50  		return nil
    51  	}
    52  	return content
    53  }
    54  
    55  // ProposalType returns the proposal type
    56  func (p Proposal) ProposalType() string {
    57  	content := p.GetContent()
    58  	if content == nil {
    59  		return ""
    60  	}
    61  	return content.ProposalType()
    62  }
    63  
    64  // ProposalRoute returns the proposal route
    65  func (p Proposal) ProposalRoute() string {
    66  	content := p.GetContent()
    67  	if content == nil {
    68  		return ""
    69  	}
    70  	return content.ProposalRoute()
    71  }
    72  
    73  // GetTitle gets the proposal's title
    74  func (p Proposal) GetTitle() string {
    75  	content := p.GetContent()
    76  	if content == nil {
    77  		return ""
    78  	}
    79  	return content.GetTitle()
    80  }
    81  
    82  // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
    83  func (p Proposal) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
    84  	var content Content
    85  	return unpacker.UnpackAny(p.Content, &content)
    86  }
    87  
    88  // Proposals is an array of proposal
    89  type Proposals []Proposal
    90  
    91  var _ codectypes.UnpackInterfacesMessage = Proposals{}
    92  
    93  // Equal returns true if two slices (order-dependant) of proposals are equal.
    94  func (p Proposals) Equal(other Proposals) bool {
    95  	if len(p) != len(other) {
    96  		return false
    97  	}
    98  
    99  	for i, proposal := range p {
   100  		if !proposal.Equal(other[i]) {
   101  			return false
   102  		}
   103  	}
   104  
   105  	return true
   106  }
   107  
   108  // String implements stringer interface
   109  func (p Proposals) String() string {
   110  	out := "ID - (Status) [Type] Title\n"
   111  	for _, prop := range p {
   112  		out += fmt.Sprintf("%d - (%s) [%s] %s\n",
   113  			prop.ProposalId, prop.Status,
   114  			prop.ProposalType(), prop.GetTitle())
   115  	}
   116  	return strings.TrimSpace(out)
   117  }
   118  
   119  // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
   120  func (p Proposals) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
   121  	for _, x := range p {
   122  		err := x.UnpackInterfaces(unpacker)
   123  		if err != nil {
   124  			return err
   125  		}
   126  	}
   127  	return nil
   128  }
   129  
   130  // ProposalStatusFromString turns a string into a ProposalStatus
   131  func ProposalStatusFromString(str string) (ProposalStatus, error) {
   132  	num, ok := ProposalStatus_value[str]
   133  	if !ok {
   134  		return StatusNil, fmt.Errorf("'%s' is not a valid proposal status", str)
   135  	}
   136  	return ProposalStatus(num), nil
   137  }
   138  
   139  // Format implements the fmt.Formatter interface.
   140  func (status ProposalStatus) Format(s fmt.State, verb rune) {
   141  	switch verb {
   142  	case 's':
   143  		s.Write([]byte(status.String()))
   144  	default:
   145  		// TODO: Do this conversion more directly
   146  		s.Write([]byte(fmt.Sprintf("%v", byte(status))))
   147  	}
   148  }
   149  
   150  // Proposal types
   151  const (
   152  	ProposalTypeText string = "Text"
   153  
   154  	// Constants pertaining to a Content object
   155  	MaxDescriptionLength int = 10000
   156  	MaxTitleLength       int = 140
   157  )
   158  
   159  // Implements Content Interface
   160  var _ Content = &TextProposal{}
   161  
   162  // NewTextProposal creates a text proposal Content
   163  func NewTextProposal(title, description string) Content {
   164  	return &TextProposal{title, description}
   165  }
   166  
   167  // GetTitle returns the proposal title
   168  func (tp *TextProposal) GetTitle() string { return tp.Title }
   169  
   170  // GetDescription returns the proposal description
   171  func (tp *TextProposal) GetDescription() string { return tp.Description }
   172  
   173  // ProposalRoute returns the proposal router key
   174  func (tp *TextProposal) ProposalRoute() string { return types.RouterKey }
   175  
   176  // ProposalType is "Text"
   177  func (tp *TextProposal) ProposalType() string { return ProposalTypeText }
   178  
   179  // ValidateBasic validates the content's title and description of the proposal
   180  func (tp *TextProposal) ValidateBasic() error { return ValidateAbstract(tp) }
   181  
   182  // ValidProposalStatus checks if the proposal status is valid
   183  func ValidProposalStatus(status ProposalStatus) bool {
   184  	if status == StatusDepositPeriod ||
   185  		status == StatusVotingPeriod ||
   186  		status == StatusPassed ||
   187  		status == StatusRejected ||
   188  		status == StatusFailed {
   189  		return true
   190  	}
   191  	return false
   192  }
   193  
   194  // ValidateAbstract validates a proposal's abstract contents returning an error
   195  // if invalid.
   196  func ValidateAbstract(c Content) error {
   197  	title := c.GetTitle()
   198  	if len(strings.TrimSpace(title)) == 0 {
   199  		return errorsmod.Wrap(types.ErrInvalidProposalContent, "proposal title cannot be blank")
   200  	}
   201  	if len(title) > MaxTitleLength {
   202  		return errorsmod.Wrapf(types.ErrInvalidProposalContent, "proposal title is longer than max length of %d", MaxTitleLength)
   203  	}
   204  
   205  	description := c.GetDescription()
   206  	if len(description) == 0 {
   207  		return errorsmod.Wrap(types.ErrInvalidProposalContent, "proposal description cannot be blank")
   208  	}
   209  	if len(description) > MaxDescriptionLength {
   210  		return errorsmod.Wrapf(types.ErrInvalidProposalContent, "proposal description is longer than max length of %d", MaxDescriptionLength)
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  var validProposalTypes = map[string]struct{}{
   217  	ProposalTypeText: {},
   218  }
   219  
   220  // RegisterProposalType registers a proposal type. It will panic if the type is
   221  // already registered.
   222  func RegisterProposalType(ty string) {
   223  	if _, ok := validProposalTypes[ty]; ok {
   224  		panic(fmt.Sprintf("already registered proposal type: %s", ty))
   225  	}
   226  
   227  	validProposalTypes[ty] = struct{}{}
   228  }
   229  
   230  // ContentFromProposalType returns a Content object based on the proposal type.
   231  func ContentFromProposalType(title, desc, ty string) (Content, bool) {
   232  	if strings.EqualFold(ty, ProposalTypeText) {
   233  		return NewTextProposal(title, desc), true
   234  	}
   235  
   236  	return nil, false
   237  }
   238  
   239  // IsValidProposalType returns a boolean determining if the proposal type is
   240  // valid.
   241  //
   242  // NOTE: Modules with their own proposal types must register them.
   243  func IsValidProposalType(ty string) bool {
   244  	_, ok := validProposalTypes[ty]
   245  	return ok
   246  }
   247  
   248  // ProposalHandler implements the Handler interface for governance module-based
   249  // proposals (ie. TextProposal ). Since these are
   250  // merely signaling mechanisms at the moment and do not affect state, it
   251  // performs a no-op.
   252  func ProposalHandler(_ sdk.Context, c Content) error {
   253  	switch c.ProposalType() {
   254  	case ProposalTypeText:
   255  		// both proposal types do not change state so this performs a no-op
   256  		return nil
   257  
   258  	default:
   259  		return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized gov proposal type: %s", c.ProposalType())
   260  	}
   261  }