package types import ( "encoding/json" "fmt" "strings" "time" "gopkg.in/yaml.v2" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // DefaultStartingProposalID is 1 const DefaultStartingProposalID uint64 = 1 // Proposal defines a struct used by the governance module to allow for voting // on network changes. type Proposal struct { Content `json:"content" yaml:"content"` // Proposal content interface ProposalBase } // NewProposal creates a new Proposal instance func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) Proposal { return Proposal{ Content: content, ProposalBase: ProposalBase{ ProposalID: id, Status: StatusDepositPeriod, FinalTallyResult: EmptyTallyResult(), TotalDeposit: sdk.NewCoins(), SubmitTime: submitTime, DepositEndTime: depositEndTime, }, } } // String implements stringer interface func (p Proposal) String() string { out, _ := yaml.Marshal(p) return string(out) } // Proposals is an array of proposal type Proposals []Proposal // String implements stringer interface func (p Proposals) String() string { out := "ID - (Status) [Type] Title\n" for _, prop := range p { out += fmt.Sprintf("%d - (%s) [%s] %s\n", prop.ProposalID, prop.Status, prop.ProposalType(), prop.GetTitle()) } return strings.TrimSpace(out) } type ( // ProposalQueue defines a queue for proposal ids ProposalQueue []uint64 ) // ProposalStatusFromString turns a string into a ProposalStatus func ProposalStatusFromString(str string) (ProposalStatus, error) { switch str { case "DepositPeriod": return StatusDepositPeriod, nil case "VotingPeriod": return StatusVotingPeriod, nil case "Passed": return StatusPassed, nil case "Rejected": return StatusRejected, nil case "Failed": return StatusFailed, nil case "": return StatusNil, nil default: return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str) } } // ValidProposalStatus returns true if the proposal status is valid and false // otherwise. func ValidProposalStatus(status ProposalStatus) bool { if status == StatusDepositPeriod || status == StatusVotingPeriod || status == StatusPassed || status == StatusRejected || status == StatusFailed { return true } return false } // Marshal needed for protobuf compatibility func (status ProposalStatus) Marshal() ([]byte, error) { return []byte{byte(status)}, nil } // Unmarshal needed for protobuf compatibility func (status *ProposalStatus) Unmarshal(data []byte) error { *status = ProposalStatus(data[0]) return nil } // MarshalJSON Marshals to JSON using string representation of the status func (status ProposalStatus) MarshalJSON() ([]byte, error) { return json.Marshal(status.String()) } // UnmarshalJSON Unmarshals from JSON assuming Bech32 encoding func (status *ProposalStatus) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { return err } bz2, err := ProposalStatusFromString(s) if err != nil { return err } *status = bz2 return nil } // String implements the Stringer interface. func (status ProposalStatus) String() string { switch status { case StatusDepositPeriod: return "DepositPeriod" case StatusVotingPeriod: return "VotingPeriod" case StatusPassed: return "Passed" case StatusRejected: return "Rejected" case StatusFailed: return "Failed" default: return "" } } // Format implements the fmt.Formatter interface. // nolint: errcheck func (status ProposalStatus) Format(s fmt.State, verb rune) { switch verb { case 's': s.Write([]byte(status.String())) default: // TODO: Do this conversion more directly s.Write([]byte(fmt.Sprintf("%v", byte(status)))) } } // Proposal types const ( ProposalTypeText string = "Text" ) // NewTextProposal creates a text proposal Content func NewTextProposal(title, description string) Content { return TextProposal{title, description} } // Implements Content Interface var _ Content = TextProposal{} // GetTitle returns the proposal title func (tp TextProposal) GetTitle() string { return tp.Title } // GetDescription returns the proposal description func (tp TextProposal) GetDescription() string { return tp.Description } // ProposalRoute returns the proposal router key func (tp TextProposal) ProposalRoute() string { return RouterKey } // ProposalType is "Text" func (tp TextProposal) ProposalType() string { return ProposalTypeText } // ValidateBasic validates the content's title and description of the proposal func (tp TextProposal) ValidateBasic() error { return ValidateAbstract(tp) } // String implements Stringer interface func (tp TextProposal) String() string { return fmt.Sprintf(`Text Proposal: Title: %s Description: %s `, tp.Title, tp.Description) } var validProposalTypes = map[string]struct{}{ ProposalTypeText: {}, } // RegisterProposalType registers a proposal type. It will panic if the type is // already registered. func RegisterProposalType(ty string) { if _, ok := validProposalTypes[ty]; ok { panic(fmt.Sprintf("already registered proposal type: %s", ty)) } validProposalTypes[ty] = struct{}{} } // ContentFromProposalType returns a Content object based on the proposal type. func ContentFromProposalType(title, desc, ty string) Content { switch ty { case ProposalTypeText: return NewTextProposal(title, desc) default: return nil } } // IsValidProposalType returns a boolean determining if the proposal type is // valid. // // NOTE: Modules with their own proposal types must register them. func IsValidProposalType(ty string) bool { _, ok := validProposalTypes[ty] return ok } // ProposalHandler implements the Handler interface for governance module-based // proposals (ie. TextProposal ). Since these are // merely signaling mechanisms at the moment and do not affect state, it // performs a no-op. func ProposalHandler(_ sdk.Context, c Content) error { switch c.ProposalType() { case ProposalTypeText: // both proposal types do not change state so this performs a no-op return nil default: return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized gov proposal type: %s", c.ProposalType()) } }