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  }