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 }