2022-01-08 00:39:27 +00:00
package v0_15
import (
"encoding/json"
"fmt"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
v036gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v036"
)
const MaxCommitteeDescriptionLength int = 512
type TallyOption uint64
const (
NullTallyOption TallyOption = iota
FirstPastThePost TallyOption = iota // Votes are tallied each block and the proposal passes as soon as the vote threshold is reached
Deadline TallyOption = iota // Votes are tallied exactly once, when the deadline time is reached
)
const (
BaseCommitteeType = "kava/BaseCommittee"
MemberCommitteeType = "kava/MemberCommittee" // Committee is composed of member addresses that vote to enact proposals within their permissions
TokenCommitteeType = "kava/TokenCommittee" // Committee is composed of token holders with voting power determined by total token balance
BondDenom = "ukava"
)
// TallyOptionFromString returns a TallyOption from a string. It returns an error
// if the string is invalid.
func TallyOptionFromString ( str string ) ( TallyOption , error ) {
switch strings . ToLower ( str ) {
case "firstpastthepost" , "fptp" :
return FirstPastThePost , nil
case "deadline" , "d" :
return Deadline , nil
default :
return TallyOption ( 0xff ) , fmt . Errorf ( "'%s' is not a valid tally option" , str )
}
}
// Marshal needed for protobuf compatibility.
func ( t TallyOption ) Marshal ( ) ( [ ] byte , error ) {
return [ ] byte { byte ( t ) } , nil
}
// Unmarshal needed for protobuf compatibility.
func ( t * TallyOption ) Unmarshal ( data [ ] byte ) error {
* t = TallyOption ( data [ 0 ] )
return nil
}
// Marshals to JSON using string.
func ( t TallyOption ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( t . String ( ) )
}
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
func ( t * TallyOption ) UnmarshalJSON ( data [ ] byte ) error {
var s string
err := json . Unmarshal ( data , & s )
if err != nil {
return err
}
bz2 , err := TallyOptionFromString ( s )
if err != nil {
return err
}
* t = bz2
return nil
}
// String implements the Stringer interface.
func ( t TallyOption ) String ( ) string {
switch t {
case FirstPastThePost :
return "FirstPastThePost"
case Deadline :
return "Deadline"
default :
return ""
}
}
// Committee is an interface for handling common actions on committees
type Committee interface {
GetID ( ) uint64
GetType ( ) string
GetDescription ( ) string
GetMembers ( ) [ ] sdk . AccAddress
SetMembers ( [ ] sdk . AccAddress ) BaseCommittee
HasMember ( addr sdk . AccAddress ) bool
GetPermissions ( ) [ ] Permission
SetPermissions ( [ ] Permission ) Committee
GetProposalDuration ( ) time . Duration
SetProposalDuration ( time . Duration ) BaseCommittee
GetVoteThreshold ( ) sdk . Dec
SetVoteThreshold ( sdk . Dec ) BaseCommittee
GetTallyOption ( ) TallyOption
Validate ( ) error
}
var (
_ Committee = MemberCommittee { }
_ Committee = TokenCommittee { }
)
// Committees is a slice of committees
type Committees [ ] Committee
// BaseCommittee is a common type shared by all Committees
type BaseCommittee struct {
ID uint64 ` json:"id" yaml:"id" `
Description string ` json:"description" yaml:"description" `
Members [ ] sdk . AccAddress ` json:"members" yaml:"members" `
Permissions [ ] Permission ` json:"permissions" yaml:"permissions" `
VoteThreshold sdk . Dec ` json:"vote_threshold" yaml:"vote_threshold" ` // Smallest percentage that must vote for a proposal to pass
ProposalDuration time . Duration ` json:"proposal_duration" yaml:"proposal_duration" ` // The length of time a proposal remains active for. Proposals will close earlier if they get enough votes.
TallyOption TallyOption ` json:"tally_option" yaml:"tally_option" `
}
// GetType is a getter for committee type
func ( c BaseCommittee ) GetType ( ) string { return BaseCommitteeType }
// GetID is a getter for committee ID
func ( c BaseCommittee ) GetID ( ) uint64 { return c . ID }
// GetDescription is a getter for committee description
func ( c BaseCommittee ) GetDescription ( ) string { return c . Description }
// GetMembers is a getter for committee members
func ( c BaseCommittee ) GetMembers ( ) [ ] sdk . AccAddress { return c . Members }
// SetMembers is a setter for committee members
func ( c BaseCommittee ) SetMembers ( members [ ] sdk . AccAddress ) BaseCommittee {
c . Members = members
return c
}
// HasMember returns if a committee contains a given member address
func ( c BaseCommittee ) HasMember ( addr sdk . AccAddress ) bool {
for _ , m := range c . Members {
if m . Equals ( addr ) {
return true
}
}
return false
}
// GetPermissions is a getter for committee permissions
func ( c BaseCommittee ) GetPermissions ( ) [ ] Permission { return c . Permissions }
// SetPermissions is a setter for committee permissions
func ( c BaseCommittee ) SetPermissions ( permissions [ ] Permission ) BaseCommittee {
c . Permissions = permissions
return c
}
// GetVoteThreshold is a getter for committee VoteThreshold
func ( c BaseCommittee ) GetVoteThreshold ( ) sdk . Dec { return c . VoteThreshold }
// SetVoteThreshold is a setter for committee VoteThreshold
func ( c BaseCommittee ) SetVoteThreshold ( voteThreshold sdk . Dec ) BaseCommittee {
c . VoteThreshold = voteThreshold
return c
}
// GetProposalDuration is a getter for committee ProposalDuration
func ( c BaseCommittee ) GetProposalDuration ( ) time . Duration { return c . ProposalDuration }
// SetProposalDuration is a setter for committee ProposalDuration
func ( c BaseCommittee ) SetProposalDuration ( proposalDuration time . Duration ) BaseCommittee {
c . ProposalDuration = proposalDuration
return c
}
// GetTallyOption is a getter for committee TallyOption
func ( c BaseCommittee ) GetTallyOption ( ) TallyOption { return c . TallyOption }
// Validate validates BaseCommittee fields
func ( c BaseCommittee ) Validate ( ) error {
if len ( c . Description ) > MaxCommitteeDescriptionLength {
return fmt . Errorf ( "description length %d longer than max allowed %d" , len ( c . Description ) , MaxCommitteeDescriptionLength )
}
if len ( c . Members ) <= 0 {
return fmt . Errorf ( "committee must have members" )
}
addressMap := make ( map [ string ] bool , len ( c . Members ) )
for _ , m := range c . Members {
// check there are no duplicate members
if _ , ok := addressMap [ m . String ( ) ] ; ok {
return fmt . Errorf ( "committee cannot have duplicate members, %s" , m )
}
// check for valid addresses
if m . Empty ( ) {
return fmt . Errorf ( "committee cannot have empty member address" )
}
addressMap [ m . String ( ) ] = true
}
for _ , p := range c . Permissions {
if p == nil {
return fmt . Errorf ( "committee cannot have a nil permission" )
}
}
if c . ProposalDuration < 0 {
return fmt . Errorf ( "invalid proposal duration: %s" , c . ProposalDuration )
}
// threshold must be in the range [0, 1]
if c . VoteThreshold . IsNil ( ) || c . VoteThreshold . LTE ( sdk . ZeroDec ( ) ) || c . VoteThreshold . GT ( sdk . NewDec ( 1 ) ) {
return fmt . Errorf ( "invalid threshold: %s" , c . VoteThreshold )
}
if c . TallyOption <= 0 || c . TallyOption > 2 {
return fmt . Errorf ( "invalid tally option: %d" , c . TallyOption )
}
return nil
}
// MemberCommittee is an alias of BaseCommittee
type MemberCommittee struct {
BaseCommittee ` json:"base_committee" yaml:"base_committee" `
}
// NewMemberCommittee instantiates a new instance of MemberCommittee
func NewMemberCommittee ( id uint64 , description string , members [ ] sdk . AccAddress , permissions [ ] Permission ,
2022-05-09 18:37:36 +00:00
threshold sdk . Dec , duration time . Duration , tallyOption TallyOption ,
) MemberCommittee {
2022-01-08 00:39:27 +00:00
return MemberCommittee {
BaseCommittee : BaseCommittee {
ID : id ,
Description : description ,
Members : members ,
Permissions : permissions ,
VoteThreshold : threshold ,
ProposalDuration : duration ,
TallyOption : tallyOption ,
} ,
}
}
// GetType is a getter for committee type
func ( c MemberCommittee ) GetType ( ) string { return MemberCommitteeType }
// SetPermissions is a setter for committee permissions
func ( c MemberCommittee ) SetPermissions ( permissions [ ] Permission ) Committee {
c . Permissions = permissions
return c
}
// Validate validates the committee's fields
func ( c MemberCommittee ) Validate ( ) error {
return c . BaseCommittee . Validate ( )
}
// TokenCommittee supports voting on proposals by token holders
type TokenCommittee struct {
BaseCommittee ` json:"base_committee" yaml:"base_committee" `
Quorum sdk . Dec ` json:"quorum" yaml:"quorum" `
TallyDenom string ` json:"tally_denom" yaml:"tally_denom" `
}
// NewTokenCommittee instantiates a new instance of TokenCommittee
func NewTokenCommittee ( id uint64 , description string , members [ ] sdk . AccAddress , permissions [ ] Permission ,
2022-05-09 18:37:36 +00:00
threshold sdk . Dec , duration time . Duration , tallyOption TallyOption , quorum sdk . Dec , tallyDenom string ,
) TokenCommittee {
2022-01-08 00:39:27 +00:00
return TokenCommittee {
BaseCommittee : BaseCommittee {
ID : id ,
Description : description ,
Members : members ,
Permissions : permissions ,
VoteThreshold : threshold ,
ProposalDuration : duration ,
TallyOption : tallyOption ,
} ,
Quorum : quorum ,
TallyDenom : tallyDenom ,
}
}
// GetType is a getter for committee type
func ( c TokenCommittee ) GetType ( ) string { return TokenCommitteeType }
// GetQuorum returns the quorum of the committee
func ( c TokenCommittee ) GetQuorum ( ) sdk . Dec { return c . Quorum }
// GetTallyDenom returns the tally denom of the committee
func ( c TokenCommittee ) GetTallyDenom ( ) string { return c . TallyDenom }
// SetPermissions is a setter for committee permissions
func ( c TokenCommittee ) SetPermissions ( permissions [ ] Permission ) Committee {
c . Permissions = permissions
return c
}
// Validate validates the committee's fields
func ( c TokenCommittee ) Validate ( ) error {
if c . TallyDenom == BondDenom {
return fmt . Errorf ( "invalid tally denom: %s" , c . TallyDenom )
}
err := sdk . ValidateDenom ( c . TallyDenom )
if err != nil {
return err
}
if c . Quorum . IsNil ( ) || c . Quorum . IsNegative ( ) || c . Quorum . GT ( sdk . NewDec ( 1 ) ) {
return fmt . Errorf ( "invalid quorum: %s" , c . Quorum )
}
return c . BaseCommittee . Validate ( )
}
// ------------------------------------------
// Proposals
// ------------------------------------------
// PubProposal is the interface that all proposals must fulfill to be submitted to a committee.
// Proposal types can be created external to this module. For example a ParamChangeProposal, or CommunityPoolSpendProposal.
// It is pinned to the equivalent type in the gov module to create compatibility between proposal types.
type PubProposal v036gov . Content
// Proposal is an internal record of a governance proposal submitted to a committee.
type Proposal struct {
PubProposal ` json:"pub_proposal" yaml:"pub_proposal" `
ID uint64 ` json:"id" yaml:"id" `
CommitteeID uint64 ` json:"committee_id" yaml:"committee_id" `
Deadline time . Time ` json:"deadline" yaml:"deadline" `
}
func NewProposal ( pubProposal PubProposal , id uint64 , committeeID uint64 , deadline time . Time ) Proposal {
return Proposal {
PubProposal : pubProposal ,
ID : id ,
CommitteeID : committeeID ,
Deadline : deadline ,
}
}
// HasExpiredBy calculates if the proposal will have expired by a certain time.
// All votes must be cast before deadline, those cast at time == deadline are not valid
func ( p Proposal ) HasExpiredBy ( time time . Time ) bool {
return ! time . Before ( p . Deadline )
}
// ------------------------------------------
// Votes
// ------------------------------------------
type Vote struct {
ProposalID uint64 ` json:"proposal_id" yaml:"proposal_id" `
Voter sdk . AccAddress ` json:"voter" yaml:"voter" `
VoteType VoteType ` json:"vote_type" yaml:"vote_type" `
}
func NewVote ( proposalID uint64 , voter sdk . AccAddress , voteType VoteType ) Vote {
return Vote {
ProposalID : proposalID ,
Voter : voter ,
VoteType : voteType ,
}
}
func ( v Vote ) Validate ( ) error {
if v . Voter . Empty ( ) {
return fmt . Errorf ( "voter address cannot be empty" )
}
return v . VoteType . Validate ( )
}
type VoteType uint64
const (
NullVoteType VoteType = iota // 0
Yes VoteType = iota // 1
No VoteType = iota // 2
Abstain VoteType = iota // 3
)
// VoteTypeFromString returns a VoteType from a string. It returns an error
// if the string is invalid.
func VoteTypeFromString ( str string ) ( VoteType , error ) {
switch strings . ToLower ( str ) {
case "yes" , "y" :
return Yes , nil
case "abstain" , "a" :
return Abstain , nil
case "no" , "n" :
return No , nil
default :
return VoteType ( 0xff ) , fmt . Errorf ( "'%s' is not a valid vote type" , str )
}
}
// Marshal needed for protobuf compatibility.
func ( vt VoteType ) Marshal ( ) ( [ ] byte , error ) {
return [ ] byte { byte ( vt ) } , nil
}
// Unmarshal needed for protobuf compatibility.
func ( vt * VoteType ) Unmarshal ( data [ ] byte ) error {
* vt = VoteType ( data [ 0 ] )
return nil
}
// Marshals to JSON using string.
func ( vt VoteType ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( vt . String ( ) )
}
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
func ( vt * VoteType ) UnmarshalJSON ( data [ ] byte ) error {
var s string
err := json . Unmarshal ( data , & s )
if err != nil {
return err
}
bz2 , err := VoteTypeFromString ( s )
if err != nil {
return err
}
* vt = bz2
return nil
}
// String implements the Stringer interface.
func ( vt VoteType ) String ( ) string {
switch vt {
case Yes :
return "Yes"
case Abstain :
return "Abstain"
case No :
return "No"
default :
return ""
}
}
func ( vt VoteType ) Validate ( ) error {
if vt <= 0 || vt > 3 {
return fmt . Errorf ( "invalid vote type: %d" , vt )
}
return nil
}
// CommitteeChangeProposal is a gov proposal for creating a new committee or modifying an existing one.
type CommitteeChangeProposal struct {
Title string ` json:"title" yaml:"title" `
Description string ` json:"description" yaml:"description" `
NewCommittee Committee ` json:"new_committee" yaml:"new_committee" `
}
// CommitteeDeleteProposal is a gov proposal for removing a committee.
type CommitteeDeleteProposal struct {
Title string ` json:"title" yaml:"title" `
Description string ` json:"description" yaml:"description" `
CommitteeID uint64 ` json:"committee_id" yaml:"committee_id" `
}