2019-04-30 09:31:38 -07:00
package types
import (
"encoding/json"
"fmt"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
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
2019-04-30 09:31:38 -07:00
2019-07-05 16:25:56 -07:00
ProposalID uint64 ` json:"id" yaml:"id" ` // ID of the proposal
Status ProposalStatus ` json:"proposal_status" yaml:"proposal_status" ` // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult ` json:"final_tally_result" yaml:"final_tally_result" ` // Result of Tallys
2019-04-30 09:31:38 -07:00
2019-07-05 16:25:56 -07:00
SubmitTime time . Time ` json:"submit_time" yaml:"submit_time" ` // Time of the block where TxGovSubmitProposal was included
DepositEndTime time . Time ` json:"deposit_end_time" yaml:"deposit_end_time" ` // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk . Coins ` json:"total_deposit" yaml:"total_deposit" ` // Current deposit on this proposal. Initial value is set at InitialDeposit
2019-04-30 09:31:38 -07:00
2019-07-05 16:25:56 -07:00
VotingStartTime time . Time ` json:"voting_start_time" yaml:"voting_start_time" ` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time . Time ` json:"voting_end_time" yaml:"voting_end_time" ` // Time that the VotingPeriod for this proposal will end and votes will be tallied
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 {
Content : content ,
ProposalID : id ,
Status : StatusDepositPeriod ,
FinalTallyResult : EmptyTallyResult ( ) ,
TotalDeposit : sdk . NewCoins ( ) ,
SubmitTime : submitTime ,
DepositEndTime : depositEndTime ,
}
}
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 {
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
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
// ProposalStatus is a type alias that represents a proposal status as a byte
ProposalStatus byte
)
2019-08-08 12:51:18 -07:00
// Valid Proposal statuses
2019-04-30 09:31:38 -07:00
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
StatusRejected ProposalStatus = 0x04
StatusFailed ProposalStatus = 0x05
)
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 (
ProposalTypeText string = "Text"
ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade"
)
2019-08-08 12:51:18 -07:00
// TextProposal defines a standard text proposal whose changes need to be
// manually updated in case of approval
2019-04-30 09:31:38 -07:00
type TextProposal struct {
2019-07-05 16:25:56 -07:00
Title string ` json:"title" yaml:"title" `
Description string ` json:"description" yaml:"description" `
2019-04-30 09:31:38 -07:00
}
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
// Implements Content Interface
2019-04-30 09:31:38 -07:00
var _ Content = TextProposal { }
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-04-30 09:31:38 -07:00
func ( tp TextProposal ) ValidateBasic ( ) sdk . Error { return ValidateAbstract ( DefaultCodespace , tp ) }
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 {
return fmt . Sprintf ( ` Text Proposal :
Title : % s
Description : % s
` , tp . Title , tp . Description )
}
2019-08-08 12:51:18 -07:00
// SoftwareUpgradeProposal defines a proposal for upgrading the network nodes
// without the need of manually halting at a given height
//
2019-04-30 09:31:38 -07:00
// TODO: We have to add fields for SUP specific arguments e.g. commit hash,
// upgrade date, etc.
type SoftwareUpgradeProposal struct {
2019-07-05 16:25:56 -07:00
Title string ` json:"title" yaml:"title" `
Description string ` json:"description" yaml:"description" `
2019-04-30 09:31:38 -07:00
}
2019-08-08 12:51:18 -07:00
// NewSoftwareUpgradeProposal creates a software upgrade proposal Content
2019-04-30 09:31:38 -07:00
func NewSoftwareUpgradeProposal ( title , description string ) Content {
return SoftwareUpgradeProposal { title , description }
}
2019-08-08 12:51:18 -07:00
// Implements Content Interface
2019-04-30 09:31:38 -07:00
var _ Content = SoftwareUpgradeProposal { }
2019-08-08 12:51:18 -07:00
// GetTitle returns the proposal title
func ( sup SoftwareUpgradeProposal ) GetTitle ( ) string { return sup . Title }
// GetDescription returns the proposal description
2019-04-30 09:31:38 -07:00
func ( sup SoftwareUpgradeProposal ) GetDescription ( ) string { return sup . Description }
2019-08-08 12:51:18 -07:00
// ProposalRoute returns the proposal router key
func ( sup SoftwareUpgradeProposal ) ProposalRoute ( ) string { return RouterKey }
// ProposalType is "SoftwareUpgrade"
func ( sup SoftwareUpgradeProposal ) ProposalType ( ) string { return ProposalTypeSoftwareUpgrade }
// ValidateBasic validates the content's title and description of the proposal
2019-04-30 09:31:38 -07:00
func ( sup SoftwareUpgradeProposal ) ValidateBasic ( ) sdk . Error {
return ValidateAbstract ( DefaultCodespace , sup )
}
2019-08-08 12:51:18 -07:00
// String implements Stringer interface
2019-04-30 09:31:38 -07:00
func ( sup SoftwareUpgradeProposal ) String ( ) string {
return fmt . Sprintf ( ` Software Upgrade Proposal :
Title : % s
Description : % s
` , sup . Title , sup . Description )
}
var validProposalTypes = map [ string ] struct { } {
2019-05-06 09:50:05 -07:00
ProposalTypeText : { } ,
ProposalTypeSoftwareUpgrade : { } ,
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 )
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 )
}
}