cosmos-sdk/x/gov/types/proposal.go

367 lines
9.8 KiB
Go

package types
import (
"encoding/json"
"fmt"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Proposal defines a struct used by the governance module to allow for voting
// on network changes.
type Proposal struct {
Content `json:"content"` // Proposal content interface
ProposalID uint64 `json:"id"` // ID of the proposal
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
}
func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) Proposal {
return Proposal{
Content: content,
ProposalID: id,
Status: StatusDepositPeriod,
FinalTallyResult: EmptyTallyResult(),
TotalDeposit: sdk.NewCoins(),
SubmitTime: submitTime,
DepositEndTime: depositEndTime,
}
}
// nolint
func (p Proposal) String() string {
return fmt.Sprintf(`Proposal %d:
Title: %s
Type: %s
Status: %s
Submit Time: %s
Deposit End Time: %s
Total Deposit: %s
Voting Start Time: %s
Voting End Time: %s
Description: %s`,
p.ProposalID, p.GetTitle(), p.ProposalType(),
p.Status, p.SubmitTime, p.DepositEndTime,
p.TotalDeposit, p.VotingStartTime, p.VotingEndTime, p.GetDescription(),
)
}
// Proposals is an array of proposal
type Proposals []Proposal
// nolint
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
ProposalQueue []uint64
// ProposalStatus is a type alias that represents a proposal status as a byte
ProposalStatus byte
)
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
StatusRejected ProposalStatus = 0x04
StatusFailed ProposalStatus = 0x05
)
// ProposalStatusToString 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
}
// Marshals to JSON using string
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// 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))))
}
}
// Tally Results
type TallyResult struct {
Yes sdk.Int `json:"yes"`
Abstain sdk.Int `json:"abstain"`
No sdk.Int `json:"no"`
NoWithVeto sdk.Int `json:"no_with_veto"`
}
func NewTallyResult(yes, abstain, no, noWithVeto sdk.Int) TallyResult {
return TallyResult{
Yes: yes,
Abstain: abstain,
No: no,
NoWithVeto: noWithVeto,
}
}
func NewTallyResultFromMap(results map[VoteOption]sdk.Dec) TallyResult {
return TallyResult{
Yes: results[OptionYes].TruncateInt(),
Abstain: results[OptionAbstain].TruncateInt(),
No: results[OptionNo].TruncateInt(),
NoWithVeto: results[OptionNoWithVeto].TruncateInt(),
}
}
// EmptyTallyResult returns an empty TallyResult.
func EmptyTallyResult() TallyResult {
return TallyResult{
Yes: sdk.ZeroInt(),
Abstain: sdk.ZeroInt(),
No: sdk.ZeroInt(),
NoWithVeto: sdk.ZeroInt(),
}
}
// Equals returns if two proposals are equal.
func (tr TallyResult) Equals(comp TallyResult) bool {
return tr.Yes.Equal(comp.Yes) &&
tr.Abstain.Equal(comp.Abstain) &&
tr.No.Equal(comp.No) &&
tr.NoWithVeto.Equal(comp.NoWithVeto)
}
func (tr TallyResult) String() string {
return fmt.Sprintf(`Tally Result:
Yes: %s
Abstain: %s
No: %s
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
}
// Proposal types
const (
ProposalTypeText string = "Text"
ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade"
)
// Text Proposal
type TextProposal struct {
Title string `json:"title"`
Description string `json:"description"`
}
func NewTextProposal(title, description string) Content {
return TextProposal{title, description}
}
// Implements Proposal Interface
var _ Content = TextProposal{}
// nolint
func (tp TextProposal) GetTitle() string { return tp.Title }
func (tp TextProposal) GetDescription() string { return tp.Description }
func (tp TextProposal) ProposalRoute() string { return RouterKey }
func (tp TextProposal) ProposalType() string { return ProposalTypeText }
func (tp TextProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, tp) }
func (tp TextProposal) String() string {
return fmt.Sprintf(`Text Proposal:
Title: %s
Description: %s
`, tp.Title, tp.Description)
}
// Software Upgrade Proposals
// TODO: We have to add fields for SUP specific arguments e.g. commit hash,
// upgrade date, etc.
type SoftwareUpgradeProposal struct {
Title string `json:"title"`
Description string `json:"description"`
}
func NewSoftwareUpgradeProposal(title, description string) Content {
return SoftwareUpgradeProposal{title, description}
}
// Implements Proposal Interface
var _ Content = SoftwareUpgradeProposal{}
// nolint
func (sup SoftwareUpgradeProposal) GetTitle() string { return sup.Title }
func (sup SoftwareUpgradeProposal) GetDescription() string { return sup.Description }
func (sup SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
func (sup SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade }
func (sup SoftwareUpgradeProposal) ValidateBasic() sdk.Error {
return ValidateAbstract(DefaultCodespace, sup)
}
func (sup SoftwareUpgradeProposal) String() string {
return fmt.Sprintf(`Software Upgrade Proposal:
Title: %s
Description: %s
`, sup.Title, sup.Description)
}
var validProposalTypes = map[string]struct{}{
ProposalTypeText: {},
ProposalTypeSoftwareUpgrade: {},
}
// 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)
case ProposalTypeSoftwareUpgrade:
return NewSoftwareUpgradeProposal(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 and SoftwareUpgradeProposal). 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) sdk.Error {
switch c.ProposalType() {
case ProposalTypeText, ProposalTypeSoftwareUpgrade:
// both proposal types do not change state so this performs a no-op
return nil
default:
errMsg := fmt.Sprintf("unrecognized gov proposal type: %s", c.ProposalType())
return sdk.ErrUnknownRequest(errMsg)
}
}