327 lines
7.5 KiB
Go
327 lines
7.5 KiB
Go
package types
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
yaml "gopkg.in/yaml.v2"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
)
|
|
|
|
// DefaultStartingProposalID is 1
|
|
const DefaultStartingProposalID uint64 = 1
|
|
|
|
// NewProposal creates a new Proposal instance
|
|
func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) (Proposal, error) {
|
|
p := Proposal{
|
|
ProposalID: id,
|
|
Status: StatusDepositPeriod,
|
|
FinalTallyResult: EmptyTallyResult(),
|
|
TotalDeposit: sdk.NewCoins(),
|
|
SubmitTime: submitTime,
|
|
DepositEndTime: depositEndTime,
|
|
}
|
|
|
|
msg, ok := content.(proto.Message)
|
|
if !ok {
|
|
return Proposal{}, fmt.Errorf("%T does not implement proto.Message", content)
|
|
}
|
|
|
|
any, err := types.NewAnyWithValue(msg)
|
|
if err != nil {
|
|
return Proposal{}, err
|
|
}
|
|
|
|
p.Content = any
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// String implements stringer interface
|
|
func (p Proposal) String() string {
|
|
out, _ := yaml.Marshal(p)
|
|
return string(out)
|
|
}
|
|
|
|
// GetContent returns the proposal Content
|
|
func (p Proposal) GetContent() Content {
|
|
content, ok := p.Content.GetCachedValue().(Content)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (p Proposal) ProposalType() string {
|
|
content := p.GetContent()
|
|
if content == nil {
|
|
return ""
|
|
}
|
|
return content.ProposalType()
|
|
}
|
|
|
|
func (p Proposal) ProposalRoute() string {
|
|
content := p.GetContent()
|
|
if content == nil {
|
|
return ""
|
|
}
|
|
return content.ProposalRoute()
|
|
}
|
|
|
|
func (p Proposal) GetTitle() string {
|
|
content := p.GetContent()
|
|
if content == nil {
|
|
return ""
|
|
}
|
|
return content.GetTitle()
|
|
}
|
|
|
|
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
|
|
func (p Proposal) UnpackInterfaces(unpacker types.AnyUnpacker) error {
|
|
var content Content
|
|
return unpacker.UnpackAny(p.Content, &content)
|
|
}
|
|
|
|
// Proposals is an array of proposal
|
|
type Proposals []Proposal
|
|
|
|
var _ types.UnpackInterfacesMessage = Proposals{}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
|
|
func (p Proposals) UnpackInterfaces(unpacker types.AnyUnpacker) error {
|
|
for _, x := range p {
|
|
err := x.UnpackInterfaces(unpacker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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"
|
|
)
|
|
|
|
// Implements Content Interface
|
|
var _ Content = &TextProposal{}
|
|
|
|
// NewTextProposal creates a text proposal Content
|
|
func NewTextProposal(title, description string) Content {
|
|
return &TextProposal{title, description}
|
|
}
|
|
|
|
// 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 {
|
|
out, _ := yaml.Marshal(tp)
|
|
return string(out)
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|