github.com/Finschia/finschia-sdk@v0.48.1/x/foundation/foundation.go (about) 1 package foundation 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/gogo/protobuf/proto" 8 9 "github.com/Finschia/finschia-sdk/codec" 10 codectypes "github.com/Finschia/finschia-sdk/codec/types" 11 sdk "github.com/Finschia/finschia-sdk/types" 12 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 13 paramtypes "github.com/Finschia/finschia-sdk/x/params/types" 14 ) 15 16 func validateProposers(proposers []string) error { 17 if len(proposers) == 0 { 18 return sdkerrors.ErrInvalidRequest.Wrap("no proposers") 19 } 20 21 addrs := map[string]bool{} 22 for _, proposer := range proposers { 23 if addrs[proposer] { 24 return sdkerrors.ErrInvalidRequest.Wrapf("duplicated proposer: %s", proposer) 25 } 26 addrs[proposer] = true 27 28 if _, err := sdk.AccAddressFromBech32(proposer); err != nil { 29 return sdkerrors.ErrInvalidAddress.Wrapf("invalid proposer address: %s", proposer) 30 } 31 } 32 33 return nil 34 } 35 36 func validateMsgs(msgs []sdk.Msg) error { 37 if len(msgs) == 0 { 38 return sdkerrors.ErrInvalidRequest.Wrap("no msgs") 39 } 40 41 for i, msg := range msgs { 42 if err := msg.ValidateBasic(); err != nil { 43 return sdkerrors.Wrapf(err, "msg %d", i) 44 } 45 } 46 47 return nil 48 } 49 50 func validateVoteOption(option VoteOption) error { 51 if option == VOTE_OPTION_UNSPECIFIED { 52 return sdkerrors.ErrInvalidRequest.Wrap("empty vote option") 53 } 54 if _, ok := VoteOption_name[int32(option)]; !ok { 55 return sdkerrors.ErrInvalidRequest.Wrap("invalid vote option") 56 } 57 58 return nil 59 } 60 61 func (c Censorship) ValidateBasic() error { 62 url := c.MsgTypeUrl 63 if len(url) == 0 { 64 return sdkerrors.ErrInvalidRequest.Wrap("empty msg type url") 65 } 66 67 authority := c.Authority 68 if _, found := CensorshipAuthority_name[int32(authority)]; !found { 69 return sdkerrors.ErrInvalidRequest.Wrapf("censorship authority %s over %s", authority, url) 70 } 71 72 return nil 73 } 74 75 func (p Params) ValidateBasic() error { 76 if err := validateRatio(p.FoundationTax, ParamKeyFoundationTax); err != nil { 77 return err 78 } 79 80 return nil 81 } 82 83 // ParamSetPairs implements params.ParamSet 84 func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { 85 return paramtypes.ParamSetPairs{ 86 paramtypes.NewParamSetPair([]byte(ParamKeyFoundationTax), &p.FoundationTax, func(i interface{}) error { 87 v, ok := i.(sdk.Dec) 88 if !ok { 89 return sdkerrors.Wrap(sdkerrors.ErrInvalidType.Wrapf("%T", i), ParamKeyFoundationTax) 90 } 91 92 return validateRatio(v, ParamKeyFoundationTax) 93 }), 94 } 95 } 96 97 func ParamKeyTable() paramtypes.KeyTable { 98 return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) 99 } 100 101 func (m Member) ValidateBasic() error { 102 if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { 103 return sdkerrors.ErrInvalidAddress.Wrapf("invalid member address: %s", m.Address) 104 } 105 106 return nil 107 } 108 109 // ValidateBasic performs stateless validation on a member. 110 func (m MemberRequest) ValidateBasic() error { 111 if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { 112 return sdkerrors.ErrInvalidAddress.Wrapf("invalid member address: %s", m.Address) 113 } 114 115 return nil 116 } 117 118 type DecisionPolicyResult struct { 119 Allow bool 120 Final bool 121 } 122 123 type DecisionPolicy interface { 124 codec.ProtoMarshaler 125 126 // GetVotingPeriod returns the duration after proposal submission where 127 // votes are accepted. 128 GetVotingPeriod() time.Duration 129 // Allow defines policy-specific logic to allow a proposal to pass or not, 130 // based on its tally result, the foundation's total power and the time since 131 // the proposal was submitted. 132 Allow(tallyResult TallyResult, totalPower sdk.Dec, sinceSubmission time.Duration) (*DecisionPolicyResult, error) 133 134 ValidateBasic() error 135 Validate(info FoundationInfo, config Config) error 136 } 137 138 // DefaultTallyResult returns a TallyResult with all counts set to 0. 139 func DefaultTallyResult() TallyResult { 140 return NewTallyResult(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) 141 } 142 143 func NewTallyResult(yes, abstain, no, veto sdk.Dec) TallyResult { 144 return TallyResult{ 145 YesCount: yes, 146 AbstainCount: abstain, 147 NoCount: no, 148 NoWithVetoCount: veto, 149 } 150 } 151 152 func (t *TallyResult) Add(option VoteOption) error { 153 weight := sdk.OneDec() 154 155 switch option { 156 case VOTE_OPTION_YES: 157 t.YesCount = t.YesCount.Add(weight) 158 case VOTE_OPTION_NO: 159 t.NoCount = t.NoCount.Add(weight) 160 case VOTE_OPTION_ABSTAIN: 161 t.AbstainCount = t.AbstainCount.Add(weight) 162 case VOTE_OPTION_NO_WITH_VETO: 163 t.NoWithVetoCount = t.NoWithVetoCount.Add(weight) 164 default: 165 return sdkerrors.ErrInvalidRequest.Wrapf("unknown vote option %s", option) 166 } 167 168 return nil 169 } 170 171 // TotalCounts is the sum of all weights. 172 func (t TallyResult) TotalCounts() sdk.Dec { 173 totalCounts := sdk.ZeroDec() 174 175 totalCounts = totalCounts.Add(t.YesCount) 176 totalCounts = totalCounts.Add(t.NoCount) 177 totalCounts = totalCounts.Add(t.AbstainCount) 178 totalCounts = totalCounts.Add(t.NoWithVetoCount) 179 180 return totalCounts 181 } 182 183 var _ codectypes.UnpackInterfacesMessage = (*Proposal)(nil) 184 185 func (p Proposal) ValidateBasic() error { 186 if p.Id == 0 { 187 return sdkerrors.ErrInvalidRequest.Wrap("id must be > 0") 188 } 189 if p.FoundationVersion == 0 { 190 return sdkerrors.ErrInvalidVersion.Wrap("foundation version must be > 0") 191 } 192 if err := validateProposers(p.Proposers); err != nil { 193 return err 194 } 195 if err := validateMsgs(p.GetMsgs()); err != nil { 196 return err 197 } 198 return nil 199 } 200 201 func (p *Proposal) GetMsgs() []sdk.Msg { 202 msgs, err := GetMsgs(p.Messages, "proposal") 203 if err != nil { 204 panic(err) 205 } 206 return msgs 207 } 208 209 func (p *Proposal) SetMsgs(msgs []sdk.Msg) error { 210 anys, err := SetMsgs(msgs) 211 if err != nil { 212 return err 213 } 214 p.Messages = anys 215 return nil 216 } 217 218 // for the tests 219 func (p Proposal) WithMsgs(msgs []sdk.Msg) *Proposal { 220 proposal := p 221 if err := proposal.SetMsgs(msgs); err != nil { 222 return nil 223 } 224 return &proposal 225 } 226 227 func (p Proposal) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { 228 return UnpackInterfaces(unpacker, p.Messages) 229 } 230 231 // UnpackInterfaces unpacks Any's to sdk.Msg's. 232 func UnpackInterfaces(unpacker codectypes.AnyUnpacker, anys []*codectypes.Any) error { 233 for _, any := range anys { 234 var msg sdk.Msg 235 err := unpacker.UnpackAny(any, &msg) 236 if err != nil { 237 return err 238 } 239 } 240 241 return nil 242 } 243 244 // GetMsgs takes a slice of Any's and turn them into sdk.Msg's. 245 func GetMsgs(anys []*codectypes.Any, name string) ([]sdk.Msg, error) { 246 msgs := make([]sdk.Msg, len(anys)) 247 for i, any := range anys { 248 cached := any.GetCachedValue() 249 if cached == nil { 250 return nil, fmt.Errorf("any cached value is nil, %s messages must be correctly packed any values", name) 251 } 252 msgs[i] = cached.(sdk.Msg) 253 } 254 return msgs, nil 255 } 256 257 // SetMsgs takes a slice of sdk.Msg's and turn them into Any's. 258 func SetMsgs(msgs []sdk.Msg) ([]*codectypes.Any, error) { 259 anys := make([]*codectypes.Any, len(msgs)) 260 for i, msg := range msgs { 261 var err error 262 anys[i], err = codectypes.NewAnyWithValue(msg) 263 if err != nil { 264 return nil, err 265 } 266 } 267 return anys, nil 268 } 269 270 func validateDecisionPolicyWindows(windows DecisionPolicyWindows, config Config) error { 271 if windows.MinExecutionPeriod >= windows.VotingPeriod+config.MaxExecutionPeriod { 272 return sdkerrors.ErrInvalidRequest.Wrap("min_execution_period should be smaller than voting_period + max_execution_period") 273 } 274 return nil 275 } 276 277 func validateDecisionPolicyWindowsBasic(windows *DecisionPolicyWindows) error { 278 if windows == nil || windows.VotingPeriod == 0 { 279 return sdkerrors.ErrInvalidRequest.Wrap("voting period cannot be zero") 280 } 281 282 return nil 283 } 284 285 var _ DecisionPolicy = (*ThresholdDecisionPolicy)(nil) 286 287 func (p ThresholdDecisionPolicy) Allow(result TallyResult, totalWeight sdk.Dec, sinceSubmission time.Duration) (*DecisionPolicyResult, error) { 288 if sinceSubmission < p.Windows.MinExecutionPeriod { 289 return nil, sdkerrors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission) 290 } 291 292 // the real threshold of the policy is `min(threshold,total_weight)`. If 293 // the foundation member weights changes (member leaving, member weight update) 294 // and the threshold doesn't, we can end up with threshold > total_weight. 295 // In this case, as long as everyone votes yes (in which case 296 // `yesCount`==`realThreshold`), then the proposal still passes. 297 realThreshold := sdk.MinDec(p.Threshold, totalWeight) 298 if result.YesCount.GTE(realThreshold) { 299 return &DecisionPolicyResult{Allow: true, Final: true}, nil 300 } 301 302 totalCounts := result.TotalCounts() 303 undecided := totalWeight.Sub(totalCounts) 304 305 // maxYesCount is the max potential number of yes count, i.e the current yes count 306 // plus all undecided count (supposing they all vote yes). 307 maxYesCount := result.YesCount.Add(undecided) 308 if maxYesCount.LT(realThreshold) { 309 return &DecisionPolicyResult{Final: true}, nil 310 } 311 312 return &DecisionPolicyResult{}, nil 313 } 314 315 func (p ThresholdDecisionPolicy) GetVotingPeriod() time.Duration { 316 return p.Windows.VotingPeriod 317 } 318 319 func (p ThresholdDecisionPolicy) ValidateBasic() error { 320 if p.Threshold.IsNil() || !p.Threshold.IsPositive() { 321 return sdkerrors.ErrInvalidRequest.Wrap("threshold must be a positive number") 322 } 323 324 if err := validateDecisionPolicyWindowsBasic(p.Windows); err != nil { 325 return err 326 } 327 return nil 328 } 329 330 func (p ThresholdDecisionPolicy) Validate(info FoundationInfo, config Config) error { 331 if !info.TotalWeight.IsPositive() { 332 return sdkerrors.ErrInvalidRequest.Wrapf("total weight must be positive") 333 } 334 335 if err := validateDecisionPolicyWindows(*p.Windows, config); err != nil { 336 return err 337 } 338 339 return nil 340 } 341 342 var _ DecisionPolicy = (*PercentageDecisionPolicy)(nil) 343 344 func (p PercentageDecisionPolicy) Allow(result TallyResult, totalWeight sdk.Dec, sinceSubmission time.Duration) (*DecisionPolicyResult, error) { 345 if sinceSubmission < p.Windows.MinExecutionPeriod { 346 return nil, sdkerrors.ErrUnauthorized.Wrapf("must wait %s after submission before execution, currently at %s", p.Windows.MinExecutionPeriod, sinceSubmission) 347 } 348 349 notAbstaining := totalWeight.Sub(result.AbstainCount) 350 // If no one votes (everyone abstains), proposal fails 351 if notAbstaining.IsZero() { 352 return &DecisionPolicyResult{Final: true}, nil 353 } 354 355 yesPercentage := result.YesCount.Quo(notAbstaining) 356 if yesPercentage.GTE(p.Percentage) { 357 return &DecisionPolicyResult{Allow: true, Final: true}, nil 358 } 359 360 totalCounts := result.TotalCounts() 361 undecided := totalWeight.Sub(totalCounts) 362 maxYesCount := result.YesCount.Add(undecided) 363 maxYesPercentage := maxYesCount.Quo(notAbstaining) 364 if maxYesPercentage.LT(p.Percentage) { 365 return &DecisionPolicyResult{Final: true}, nil 366 } 367 368 return &DecisionPolicyResult{}, nil 369 } 370 371 func (p PercentageDecisionPolicy) GetVotingPeriod() time.Duration { 372 return p.Windows.VotingPeriod 373 } 374 375 func (p PercentageDecisionPolicy) ValidateBasic() error { 376 if err := validateDecisionPolicyWindowsBasic(p.Windows); err != nil { 377 return err 378 } 379 380 if err := validateRatio(p.Percentage, "percentage"); err != nil { 381 return err 382 } 383 384 return nil 385 } 386 387 func (p PercentageDecisionPolicy) Validate(info FoundationInfo, config Config) error { 388 // total weight must be positive, because the admin is group policy 389 // (in x/group words) 390 if !info.TotalWeight.IsPositive() { 391 return sdkerrors.ErrInvalidRequest.Wrapf("total weight must be positive") 392 } 393 394 if err := validateDecisionPolicyWindows(*p.Windows, config); err != nil { 395 return err 396 } 397 398 return nil 399 } 400 401 func validateRatio(ratio sdk.Dec, name string) error { 402 if ratio.IsNil() { 403 return sdkerrors.ErrInvalidRequest.Wrapf("%s is nil", name) 404 } 405 406 if ratio.GT(sdk.OneDec()) || ratio.IsNegative() { 407 return sdkerrors.ErrInvalidRequest.Wrapf("%s must be >= 0 and <= 1", name) 408 } 409 return nil 410 } 411 412 var _ DecisionPolicy = (*OutsourcingDecisionPolicy)(nil) 413 414 func (p OutsourcingDecisionPolicy) Allow(result TallyResult, totalWeight sdk.Dec, sinceSubmission time.Duration) (*DecisionPolicyResult, error) { 415 return nil, sdkerrors.ErrInvalidRequest.Wrap(p.Description) 416 } 417 418 func (p OutsourcingDecisionPolicy) GetVotingPeriod() time.Duration { 419 return 0 420 } 421 422 func (p OutsourcingDecisionPolicy) ValidateBasic() error { 423 return nil 424 } 425 426 func (p OutsourcingDecisionPolicy) Validate(info FoundationInfo, config Config) error { 427 return sdkerrors.ErrInvalidRequest.Wrap(p.Description) 428 } 429 430 var _ codectypes.UnpackInterfacesMessage = (*FoundationInfo)(nil) 431 432 func (i FoundationInfo) GetDecisionPolicy() DecisionPolicy { 433 if i.DecisionPolicy == nil { 434 return nil 435 } 436 437 policy, ok := i.DecisionPolicy.GetCachedValue().(DecisionPolicy) 438 if !ok { 439 return nil 440 } 441 return policy 442 } 443 444 func (i *FoundationInfo) SetDecisionPolicy(policy DecisionPolicy) error { 445 msg, ok := policy.(proto.Message) 446 if !ok { 447 return sdkerrors.ErrInvalidType.Wrapf("can't proto marshal %T", msg) 448 } 449 450 any, err := codectypes.NewAnyWithValue(msg) 451 if err != nil { 452 return err 453 } 454 i.DecisionPolicy = any 455 456 return nil 457 } 458 459 // for the tests 460 func (i FoundationInfo) WithDecisionPolicy(policy DecisionPolicy) *FoundationInfo { 461 info := i 462 if err := info.SetDecisionPolicy(policy); err != nil { 463 return nil 464 } 465 return &info 466 } 467 468 func (i *FoundationInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { 469 var policy DecisionPolicy 470 return unpacker.UnpackAny(i.DecisionPolicy, &policy) 471 } 472 473 func GetAuthorization(any *codectypes.Any, name string) (Authorization, error) { 474 cached := any.GetCachedValue() 475 if cached == nil { 476 return nil, fmt.Errorf("any cached value is nil, %s authorization must be correctly packed any values", name) 477 } 478 479 a, ok := cached.(Authorization) 480 if !ok { 481 return nil, sdkerrors.ErrInvalidType.Wrapf("can't proto unmarshal %T", a) 482 } 483 return a, nil 484 } 485 486 func SetAuthorization(a Authorization) (*codectypes.Any, error) { 487 msg, ok := a.(proto.Message) 488 if !ok { 489 return nil, sdkerrors.ErrInvalidType.Wrapf("can't proto marshal %T", msg) 490 } 491 492 any, err := codectypes.NewAnyWithValue(a) 493 if err != nil { 494 return nil, err 495 } 496 return any, nil 497 } 498 499 func (p Pool) ValidateBasic() error { 500 if err := p.Treasury.Validate(); err != nil { 501 return err 502 } 503 504 return nil 505 } 506 507 // Members defines a repeated slice of Member objects. 508 type Members struct { 509 Members []Member 510 } 511 512 // ValidateBasic performs stateless validation on an array of members. On top 513 // of validating each member individually, it also makes sure there are no 514 // duplicate addresses. 515 func (ms Members) ValidateBasic() error { 516 index := make(map[string]struct{}, len(ms.Members)) 517 for i := range ms.Members { 518 member := ms.Members[i] 519 if err := member.ValidateBasic(); err != nil { 520 return err 521 } 522 addr := member.Address 523 if _, exists := index[addr]; exists { 524 return sdkerrors.ErrInvalidRequest.Wrapf("duplicated address: %s", member.Address) 525 } 526 index[addr] = struct{}{} 527 } 528 return nil 529 } 530 531 // MemberRequests defines a repeated slice of MemberRequest objects. 532 type MemberRequests struct { 533 Members []MemberRequest 534 } 535 536 // ValidateBasic performs stateless validation on an array of members. On top 537 // of validating each member individually, it also makes sure there are no 538 // duplicate addresses. 539 func (ms MemberRequests) ValidateBasic() error { 540 index := make(map[string]struct{}, len(ms.Members)) 541 for i := range ms.Members { 542 member := ms.Members[i] 543 if err := member.ValidateBasic(); err != nil { 544 return err 545 } 546 addr := member.Address 547 if _, exists := index[addr]; exists { 548 return sdkerrors.ErrInvalidRequest.Wrapf("duplicated address: %s", member.Address) 549 } 550 index[addr] = struct{}{} 551 } 552 return nil 553 }