mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 07:47:27 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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,
 | 
						|
	threshold sdk.Dec, duration time.Duration, tallyOption TallyOption,
 | 
						|
) MemberCommittee {
 | 
						|
	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,
 | 
						|
	threshold sdk.Dec, duration time.Duration, tallyOption TallyOption, quorum sdk.Dec, tallyDenom string,
 | 
						|
) TokenCommittee {
 | 
						|
	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"`
 | 
						|
}
 |