github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/gov/types/proposal.go (about)

     1  package types
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    10  )
    11  
    12  // Proposal defines a struct used by the governance module to allow for voting
    13  // on network changes.
    14  type Proposal struct {
    15  	Content `json:"content" yaml:"content"` // Proposal content interface
    16  
    17  	ProposalID       uint64         `json:"id" yaml:"id"`                                 //  ID of the proposal
    18  	Status           ProposalStatus `json:"proposal_status" yaml:"proposal_status"`       // Status of the Proposal {Pending, Active, Passed, Rejected}
    19  	FinalTallyResult TallyResult    `json:"final_tally_result" yaml:"final_tally_result"` // Result of Tallys
    20  
    21  	SubmitTime     time.Time    `json:"submit_time" yaml:"submit_time"`           // Time of the block where TxGovSubmitProposal was included
    22  	DepositEndTime time.Time    `json:"deposit_end_time" yaml:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
    23  	TotalDeposit   sdk.SysCoins `json:"total_deposit" yaml:"total_deposit"`       // Current deposit on this proposal. Initial value is set at InitialDeposit
    24  
    25  	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
    26  	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
    27  }
    28  
    29  func NewProposal(ctx sdk.Context, totalVoting sdk.Dec, content Content, id uint64, submitTime, depositEndTime time.Time) Proposal {
    30  	return Proposal{
    31  		Content:          content,
    32  		ProposalID:       id,
    33  		Status:           StatusDepositPeriod,
    34  		FinalTallyResult: EmptyTallyResult(totalVoting),
    35  		TotalDeposit:     sdk.SysCoins{},
    36  		SubmitTime:       submitTime,
    37  		DepositEndTime:   depositEndTime,
    38  	}
    39  }
    40  
    41  // nolint
    42  func (p Proposal) String() string {
    43  	return fmt.Sprintf(`Proposal %d:
    44    Title:              %s
    45    Type:               %s
    46    Status:             %s
    47    Submit Time:        %s
    48    Deposit End Time:   %s
    49    Total Deposit:      %s
    50    Voting Start Time:  %s
    51    Voting End Time:    %s
    52    Description:        %s`,
    53  		p.ProposalID, p.GetTitle(), p.ProposalType(),
    54  		p.Status, p.SubmitTime, p.DepositEndTime,
    55  		p.TotalDeposit, p.VotingStartTime, p.VotingEndTime, p.GetDescription(),
    56  	)
    57  }
    58  
    59  // Proposals is an array of proposal
    60  type Proposals []Proposal
    61  
    62  // nolint
    63  func (p Proposals) String() string {
    64  	out := "ID - (Status) [Type] Title\n"
    65  	for _, prop := range p {
    66  		out += fmt.Sprintf("%d - (%s) [%s] %s\n",
    67  			prop.ProposalID, prop.Status,
    68  			prop.ProposalType(), prop.GetTitle())
    69  	}
    70  	return strings.TrimSpace(out)
    71  }
    72  
    73  // WrapProposalForCosmosAPI is for compatibility with the standard cosmos REST API
    74  func WrapProposalForCosmosAPI(proposal Proposal, content Content) Proposal {
    75  	return Proposal{
    76  		Content:          content,
    77  		ProposalID:       proposal.ProposalID,
    78  		Status:           proposal.Status,
    79  		FinalTallyResult: proposal.FinalTallyResult,
    80  		SubmitTime:       proposal.SubmitTime,
    81  		DepositEndTime:   proposal.DepositEndTime,
    82  		TotalDeposit:     proposal.TotalDeposit,
    83  		VotingStartTime:  proposal.VotingStartTime,
    84  		VotingEndTime:    proposal.VotingEndTime,
    85  	}
    86  }
    87  
    88  type (
    89  	// ProposalQueue
    90  	ProposalQueue []uint64
    91  
    92  	// ProposalStatus is a type alias that represents a proposal status as a byte
    93  	ProposalStatus byte
    94  )
    95  
    96  // nolint
    97  const (
    98  	StatusNil           ProposalStatus = 0x00
    99  	StatusDepositPeriod ProposalStatus = 0x01
   100  	StatusVotingPeriod  ProposalStatus = 0x02
   101  	StatusPassed        ProposalStatus = 0x03
   102  	StatusRejected      ProposalStatus = 0x04
   103  	StatusFailed        ProposalStatus = 0x05
   104  )
   105  
   106  // ProposalStatusToString turns a string into a ProposalStatus
   107  func ProposalStatusFromString(str string) (ProposalStatus, error) {
   108  	switch str {
   109  	case "DepositPeriod":
   110  		return StatusDepositPeriod, nil
   111  
   112  	case "VotingPeriod":
   113  		return StatusVotingPeriod, nil
   114  
   115  	case "Passed":
   116  		return StatusPassed, nil
   117  
   118  	case "Rejected":
   119  		return StatusRejected, nil
   120  
   121  	case "Failed":
   122  		return StatusFailed, nil
   123  
   124  	case "":
   125  		return StatusNil, nil
   126  
   127  	default:
   128  		return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
   129  	}
   130  }
   131  
   132  // ValidProposalStatus returns true if the proposal status is valid and false
   133  // otherwise.
   134  func ValidProposalStatus(status ProposalStatus) bool {
   135  	if status == StatusDepositPeriod ||
   136  		status == StatusVotingPeriod ||
   137  		status == StatusPassed ||
   138  		status == StatusRejected ||
   139  		status == StatusFailed {
   140  		return true
   141  	}
   142  	return false
   143  }
   144  
   145  // Marshal needed for protobuf compatibility
   146  func (status ProposalStatus) Marshal() ([]byte, error) {
   147  	return []byte{byte(status)}, nil
   148  }
   149  
   150  // Unmarshal needed for protobuf compatibility
   151  func (status *ProposalStatus) Unmarshal(data []byte) error {
   152  	*status = ProposalStatus(data[0])
   153  	return nil
   154  }
   155  
   156  // Marshals to JSON using string
   157  func (status ProposalStatus) MarshalJSON() ([]byte, error) {
   158  	return json.Marshal(status.String())
   159  }
   160  
   161  // Unmarshals from JSON assuming Bech32 encoding
   162  func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
   163  	var s string
   164  	err := json.Unmarshal(data, &s)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	bz2, err := ProposalStatusFromString(s)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	*status = bz2
   175  	return nil
   176  }
   177  
   178  // String implements the Stringer interface.
   179  func (status ProposalStatus) String() string {
   180  	switch status {
   181  	case StatusDepositPeriod:
   182  		return "DepositPeriod"
   183  
   184  	case StatusVotingPeriod:
   185  		return "VotingPeriod"
   186  
   187  	case StatusPassed:
   188  		return "Passed"
   189  
   190  	case StatusRejected:
   191  		return "Rejected"
   192  
   193  	case StatusFailed:
   194  		return "Failed"
   195  
   196  	default:
   197  		return ""
   198  	}
   199  }
   200  
   201  func (status ProposalStatus) MarshalYAML() (interface{}, error) {
   202  	switch status {
   203  	case StatusDepositPeriod:
   204  		return "DepositPeriod", nil
   205  
   206  	case StatusVotingPeriod:
   207  		return "VotingPeriod", nil
   208  
   209  	case StatusPassed:
   210  		return "Passed", nil
   211  
   212  	case StatusRejected:
   213  		return "Rejected", nil
   214  
   215  	case StatusFailed:
   216  		return "Failed", nil
   217  
   218  	default:
   219  		return "", nil
   220  	}
   221  }
   222  
   223  // Format implements the fmt.Formatter interface.
   224  // nolint: errcheck
   225  func (status ProposalStatus) Format(s fmt.State, verb rune) {
   226  	switch verb {
   227  	case 's':
   228  		s.Write([]byte(status.String()))
   229  	default:
   230  		// TODO: Do this conversion more directly
   231  		s.Write([]byte(fmt.Sprintf("%v", byte(status))))
   232  	}
   233  }
   234  
   235  // Tally Results
   236  type TallyResult struct {
   237  	// total power of accounts whose votes are voted to the current validator set
   238  	TotalPower sdk.Dec `json:"total_power"`
   239  	// total power of accounts who has voted for a proposal
   240  	TotalVotedPower sdk.Dec `json:"total_voted_power"`
   241  	Yes             sdk.Dec `json:"yes"`
   242  	Abstain         sdk.Dec `json:"abstain"`
   243  	No              sdk.Dec `json:"no"`
   244  	NoWithVeto      sdk.Dec `json:"no_with_veto"`
   245  }
   246  
   247  func NewTallyResult(yes, abstain, no, noWithVeto sdk.Dec) TallyResult {
   248  	return TallyResult{
   249  		Yes:        yes,
   250  		Abstain:    abstain,
   251  		No:         no,
   252  		NoWithVeto: noWithVeto,
   253  	}
   254  }
   255  
   256  func NewTallyResultFromMap(results map[VoteOption]sdk.Dec) TallyResult {
   257  	return TallyResult{
   258  		Yes:        results[OptionYes],
   259  		Abstain:    results[OptionAbstain],
   260  		No:         results[OptionNo],
   261  		NoWithVeto: results[OptionNoWithVeto],
   262  	}
   263  }
   264  
   265  // EmptyTallyResult returns an empty TallyResult.
   266  func EmptyTallyResult(totalVoting sdk.Dec) TallyResult {
   267  	return TallyResult{
   268  		TotalPower:      totalVoting,
   269  		TotalVotedPower: sdk.ZeroDec(),
   270  		Yes:             sdk.ZeroDec(),
   271  		Abstain:         sdk.ZeroDec(),
   272  		No:              sdk.ZeroDec(),
   273  		NoWithVeto:      sdk.ZeroDec(),
   274  	}
   275  }
   276  
   277  // Equals returns if two proposals are equal.
   278  func (tr TallyResult) Equals(comp TallyResult) bool {
   279  	return tr.Yes.Equal(comp.Yes) &&
   280  		tr.Abstain.Equal(comp.Abstain) &&
   281  		tr.No.Equal(comp.No) &&
   282  		tr.NoWithVeto.Equal(comp.NoWithVeto)
   283  }
   284  
   285  func (tr TallyResult) String() string {
   286  	return fmt.Sprintf(`Tally Result:
   287    TotalPower %s
   288    TotalVotedPower %s
   289    Yes:        %s
   290    Abstain:    %s
   291    No:         %s
   292    NoWithVeto: %s`, tr.TotalPower, tr.TotalVotedPower, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
   293  }
   294  
   295  // Proposal types
   296  const (
   297  	ProposalTypeText            string = "Text"
   298  	ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade"
   299  )
   300  
   301  // Text Proposal
   302  type TextProposal struct {
   303  	Title       string `json:"title" yaml:"title"`
   304  	Description string `json:"description" yaml:"description"`
   305  }
   306  
   307  func NewTextProposal(title, description string) Content {
   308  	return TextProposal{title, description}
   309  }
   310  
   311  // Implements Proposal Interface
   312  var _ Content = TextProposal{}
   313  
   314  // nolint
   315  func (tp TextProposal) GetTitle() string         { return tp.Title }
   316  func (tp TextProposal) GetDescription() string   { return tp.Description }
   317  func (tp TextProposal) ProposalRoute() string    { return RouterKey }
   318  func (tp TextProposal) ProposalType() string     { return ProposalTypeText }
   319  func (tp TextProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, tp) }
   320  
   321  func (tp TextProposal) String() string {
   322  	return fmt.Sprintf(`Text Proposal:
   323    Title:       %s
   324    Description: %s
   325  `, tp.Title, tp.Description)
   326  }
   327  
   328  // Software Upgrade Proposals
   329  // TODO: We have to add fields for SUP specific arguments e.g. commit hash,
   330  // upgrade date, etc.
   331  type SoftwareUpgradeProposal struct {
   332  	Title       string `json:"title" yaml:"title"`
   333  	Description string `json:"description" yaml:"description"`
   334  }
   335  
   336  func NewSoftwareUpgradeProposal(title, description string) Content {
   337  	return SoftwareUpgradeProposal{title, description}
   338  }
   339  
   340  // Implements Proposal Interface
   341  var _ Content = SoftwareUpgradeProposal{}
   342  
   343  // nolint
   344  func (sup SoftwareUpgradeProposal) GetTitle() string       { return sup.Title }
   345  func (sup SoftwareUpgradeProposal) GetDescription() string { return sup.Description }
   346  func (sup SoftwareUpgradeProposal) ProposalRoute() string  { return RouterKey }
   347  func (sup SoftwareUpgradeProposal) ProposalType() string   { return ProposalTypeSoftwareUpgrade }
   348  func (sup SoftwareUpgradeProposal) ValidateBasic() sdk.Error {
   349  	return ValidateAbstract(DefaultCodespace, sup)
   350  }
   351  
   352  func (sup SoftwareUpgradeProposal) String() string {
   353  	return fmt.Sprintf(`Software Upgrade Proposal:
   354    Title:       %s
   355    Description: %s
   356  `, sup.Title, sup.Description)
   357  }
   358  
   359  var validProposalTypes = map[string]struct{}{
   360  	ProposalTypeText:            {},
   361  	ProposalTypeSoftwareUpgrade: {},
   362  }
   363  
   364  // RegisterProposalType registers a proposal type. It will panic if the type is
   365  // already registered.
   366  func RegisterProposalType(ty string) {
   367  	if _, ok := validProposalTypes[ty]; ok {
   368  		panic(fmt.Sprintf("already registered proposal type: %s", ty))
   369  	}
   370  
   371  	validProposalTypes[ty] = struct{}{}
   372  }
   373  
   374  // ContentFromProposalType returns a Content object based on the proposal type.
   375  func ContentFromProposalType(title, desc, ty string) Content {
   376  	switch ty {
   377  	case ProposalTypeText:
   378  		return NewTextProposal(title, desc)
   379  
   380  	case ProposalTypeSoftwareUpgrade:
   381  		return NewSoftwareUpgradeProposal(title, desc)
   382  
   383  	default:
   384  		return nil
   385  	}
   386  }
   387  
   388  // IsValidProposalType returns a boolean determining if the proposal type is
   389  // valid.
   390  //
   391  // NOTE: Modules with their own proposal types must register them.
   392  func IsValidProposalType(ty string) bool {
   393  	_, ok := validProposalTypes[ty]
   394  	return ok
   395  }
   396  
   397  // ProposalHandler implements the Handler interface for governance module-based
   398  // proposals (ie. TextProposal and SoftwareUpgradeProposal). Since these are
   399  // merely signaling mechanisms at the moment and do not affect state, it
   400  // performs a no-op.
   401  func ProposalHandler(_ sdk.Context, p *Proposal) sdk.Error {
   402  	switch p.ProposalType() {
   403  	case ProposalTypeText, ProposalTypeSoftwareUpgrade:
   404  		// both proposal types do not change state so this performs a no-op
   405  		return nil
   406  
   407  	default:
   408  		errMsg := fmt.Sprintf("unrecognized gov proposal type: %s", p.ProposalType())
   409  		return sdk.ErrUnknownRequest(errMsg)
   410  	}
   411  }