2019-04-30 09:31:38 -07:00
|
|
|
package types
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2020-03-02 09:44:55 -08:00
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
|
2019-04-30 09:31:38 -07:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
2019-12-27 09:57:54 -08:00
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
2019-04-30 09:31:38 -07:00
|
|
|
)
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// DefaultStartingProposalID is 1
|
|
|
|
const DefaultStartingProposalID uint64 = 1
|
|
|
|
|
2019-04-30 09:31:38 -07:00
|
|
|
// Proposal defines a struct used by the governance module to allow for voting
|
|
|
|
// on network changes.
|
|
|
|
type Proposal struct {
|
2019-07-05 16:25:56 -07:00
|
|
|
Content `json:"content" yaml:"content"` // Proposal content interface
|
2020-02-26 17:47:45 -08:00
|
|
|
ProposalBase
|
2019-04-30 09:31:38 -07:00
|
|
|
}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// NewProposal creates a new Proposal instance
|
2019-04-30 09:31:38 -07:00
|
|
|
func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) Proposal {
|
|
|
|
return Proposal{
|
2020-02-26 17:47:45 -08:00
|
|
|
Content: content,
|
|
|
|
ProposalBase: ProposalBase{
|
|
|
|
ProposalID: id,
|
|
|
|
Status: StatusDepositPeriod,
|
|
|
|
FinalTallyResult: EmptyTallyResult(),
|
|
|
|
TotalDeposit: sdk.NewCoins(),
|
|
|
|
SubmitTime: submitTime,
|
|
|
|
DepositEndTime: depositEndTime,
|
|
|
|
},
|
2019-04-30 09:31:38 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 14:21:00 -08:00
|
|
|
// Equal returns true if two Proposal types are equal.
|
|
|
|
func (p Proposal) Equal(other Proposal) bool {
|
|
|
|
return p.ProposalBase.Equal(other.ProposalBase) && p.Content.String() == other.Content.String()
|
|
|
|
}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// String implements stringer interface
|
2019-04-30 09:31:38 -07:00
|
|
|
func (p Proposal) String() string {
|
2020-03-02 09:44:55 -08:00
|
|
|
out, _ := yaml.Marshal(p)
|
|
|
|
return string(out)
|
2019-04-30 09:31:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Proposals is an array of proposal
|
|
|
|
type Proposals []Proposal
|
|
|
|
|
2020-03-02 12:50:20 -08:00
|
|
|
// Equal returns true if two slices (order-dependant) of proposals are equal.
|
|
|
|
func (p Proposals) Equal(other Proposals) bool {
|
|
|
|
if len(p) != len(other) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, proposal := range p {
|
|
|
|
if !proposal.Equal(other[i]) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// String implements stringer interface
|
2019-04-30 09:31:38 -07:00
|
|
|
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 (
|
2019-08-08 12:51:18 -07:00
|
|
|
// ProposalQueue defines a queue for proposal ids
|
2019-04-30 09:31:38 -07:00
|
|
|
ProposalQueue []uint64
|
|
|
|
)
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// ProposalStatusFromString turns a string into a ProposalStatus
|
2019-04-30 09:31:38 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// MarshalJSON Marshals to JSON using string representation of the status
|
2019-04-30 09:31:38 -07:00
|
|
|
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
|
|
|
|
return json.Marshal(status.String())
|
|
|
|
}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// UnmarshalJSON Unmarshals from JSON assuming Bech32 encoding
|
2019-04-30 09:31:38 -07:00
|
|
|
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 (
|
2019-11-08 06:40:56 -08:00
|
|
|
ProposalTypeText string = "Text"
|
2019-04-30 09:31:38 -07:00
|
|
|
)
|
|
|
|
|
2020-03-02 14:21:00 -08:00
|
|
|
// Implements Content Interface
|
|
|
|
var _ Content = TextProposal{}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// NewTextProposal creates a text proposal Content
|
2019-04-30 09:31:38 -07:00
|
|
|
func NewTextProposal(title, description string) Content {
|
|
|
|
return TextProposal{title, description}
|
|
|
|
}
|
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// 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
|
2019-12-27 09:57:54 -08:00
|
|
|
func (tp TextProposal) ValidateBasic() error { return ValidateAbstract(tp) }
|
2019-04-30 09:31:38 -07:00
|
|
|
|
2019-08-08 12:51:18 -07:00
|
|
|
// String implements Stringer interface
|
2019-04-30 09:31:38 -07:00
|
|
|
func (tp TextProposal) String() string {
|
2020-03-02 11:13:42 -08:00
|
|
|
out, _ := yaml.Marshal(tp)
|
|
|
|
return string(out)
|
2019-04-30 09:31:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
var validProposalTypes = map[string]struct{}{
|
2019-11-08 06:40:56 -08:00
|
|
|
ProposalTypeText: {},
|
2019-04-30 09:31:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2019-11-08 06:40:56 -08:00
|
|
|
// proposals (ie. TextProposal ). Since these are
|
2019-04-30 09:31:38 -07:00
|
|
|
// merely signaling mechanisms at the moment and do not affect state, it
|
|
|
|
// performs a no-op.
|
2019-12-27 09:57:54 -08:00
|
|
|
func ProposalHandler(_ sdk.Context, c Content) error {
|
2019-04-30 09:31:38 -07:00
|
|
|
switch c.ProposalType() {
|
2019-11-08 06:40:56 -08:00
|
|
|
case ProposalTypeText:
|
2019-04-30 09:31:38 -07:00
|
|
|
// both proposal types do not change state so this performs a no-op
|
|
|
|
return nil
|
|
|
|
|
|
|
|
default:
|
2019-12-27 09:57:54 -08:00
|
|
|
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized gov proposal type: %s", c.ProposalType())
|
2019-04-30 09:31:38 -07:00
|
|
|
}
|
|
|
|
}
|