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 }