github.com/Finschia/finschia-sdk@v0.48.1/x/foundation/foundation_test.go (about)

     1  package foundation_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/Finschia/finschia-sdk/crypto/keys/secp256k1"
     8  	"github.com/Finschia/finschia-sdk/testutil/testdata"
     9  	sdk "github.com/Finschia/finschia-sdk/types"
    10  	"github.com/Finschia/finschia-sdk/x/foundation"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func TestTallyResult(t *testing.T) {
    15  	result := foundation.DefaultTallyResult()
    16  
    17  	err := result.Add(foundation.VOTE_OPTION_UNSPECIFIED)
    18  	require.Error(t, err)
    19  
    20  	err = result.Add(foundation.VOTE_OPTION_YES)
    21  	require.NoError(t, err)
    22  	require.Equal(t, sdk.OneDec(), result.YesCount)
    23  
    24  	result.Add(foundation.VOTE_OPTION_ABSTAIN)
    25  	require.NoError(t, err)
    26  	require.Equal(t, sdk.OneDec(), result.AbstainCount)
    27  
    28  	result.Add(foundation.VOTE_OPTION_NO)
    29  	require.NoError(t, err)
    30  	require.Equal(t, sdk.OneDec(), result.NoCount)
    31  
    32  	result.Add(foundation.VOTE_OPTION_NO_WITH_VETO)
    33  	require.NoError(t, err)
    34  	require.Equal(t, sdk.OneDec(), result.NoWithVetoCount)
    35  
    36  	require.Equal(t, sdk.NewDec(4), result.TotalCounts())
    37  }
    38  
    39  func TestThresholdDecisionPolicy(t *testing.T) {
    40  	config := foundation.DefaultConfig()
    41  
    42  	testCases := map[string]struct {
    43  		threshold          sdk.Dec
    44  		votingPeriod       time.Duration
    45  		minExecutionPeriod time.Duration
    46  		totalWeight        sdk.Dec
    47  		validBasic         bool
    48  		valid              bool
    49  	}{
    50  		"valid policy": {
    51  			threshold:          sdk.OneDec(),
    52  			votingPeriod:       time.Hour,
    53  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour - time.Nanosecond,
    54  			totalWeight:        sdk.OneDec(),
    55  			validBasic:         true,
    56  			valid:              true,
    57  		},
    58  		"invalid threshold": {
    59  			votingPeriod:       time.Hour,
    60  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour - time.Nanosecond,
    61  			totalWeight:        sdk.OneDec(),
    62  		},
    63  		"invalid voting period": {
    64  			threshold:          sdk.OneDec(),
    65  			minExecutionPeriod: config.MaxExecutionPeriod - time.Nanosecond,
    66  			totalWeight:        sdk.OneDec(),
    67  		},
    68  		"invalid min execution period": {
    69  			threshold:          sdk.OneDec(),
    70  			votingPeriod:       time.Hour,
    71  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour,
    72  			totalWeight:        sdk.OneDec(),
    73  			validBasic:         true,
    74  		},
    75  		"invalid total weight": {
    76  			threshold:          sdk.OneDec(),
    77  			votingPeriod:       time.Hour,
    78  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour - time.Nanosecond,
    79  			totalWeight:        sdk.ZeroDec(),
    80  			validBasic:         true,
    81  		},
    82  	}
    83  
    84  	for name, tc := range testCases {
    85  		t.Run(name, func(t *testing.T) {
    86  			policy := foundation.ThresholdDecisionPolicy{
    87  				Threshold: tc.threshold,
    88  				Windows: &foundation.DecisionPolicyWindows{
    89  					VotingPeriod:       tc.votingPeriod,
    90  					MinExecutionPeriod: tc.minExecutionPeriod,
    91  				},
    92  			}
    93  			require.Equal(t, tc.votingPeriod, policy.GetVotingPeriod())
    94  
    95  			err := policy.ValidateBasic()
    96  			if !tc.validBasic {
    97  				require.Error(t, err)
    98  				return
    99  			}
   100  			require.NoError(t, err)
   101  
   102  			info := foundation.FoundationInfo{
   103  				TotalWeight: tc.totalWeight,
   104  			}
   105  			err = policy.Validate(info, config)
   106  			if !tc.valid {
   107  				require.Error(t, err)
   108  				return
   109  			}
   110  			require.NoError(t, err)
   111  		})
   112  	}
   113  }
   114  
   115  func TestThresholdDecisionPolicyAllow(t *testing.T) {
   116  	config := foundation.DefaultConfig()
   117  	policy := foundation.ThresholdDecisionPolicy{
   118  		Threshold: sdk.NewDec(10),
   119  		Windows: &foundation.DecisionPolicyWindows{
   120  			VotingPeriod: time.Hour,
   121  		},
   122  	}
   123  	require.NoError(t, policy.ValidateBasic())
   124  
   125  	info := foundation.FoundationInfo{
   126  		TotalWeight: sdk.OneDec(),
   127  	}
   128  	require.NoError(t, policy.Validate(info, config))
   129  	require.Equal(t, time.Hour, policy.GetVotingPeriod())
   130  
   131  	testCases := map[string]struct {
   132  		sinceSubmission time.Duration
   133  		totalWeight     sdk.Dec
   134  		tally           foundation.TallyResult
   135  		valid           bool
   136  		final           bool
   137  		allow           bool
   138  	}{
   139  		"allow": {
   140  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   141  			totalWeight:     policy.Threshold,
   142  			tally:           foundation.NewTallyResult(policy.Threshold, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   143  			valid:           true,
   144  			final:           true,
   145  			allow:           true,
   146  		},
   147  		"allow (member size < threshold)": {
   148  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   149  			totalWeight:     sdk.OneDec(),
   150  			tally:           foundation.NewTallyResult(sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   151  			valid:           true,
   152  			final:           true,
   153  			allow:           true,
   154  		},
   155  		"not final": {
   156  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   157  			totalWeight:     policy.Threshold,
   158  			tally:           foundation.NewTallyResult(policy.Threshold.Sub(sdk.OneDec()), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   159  			valid:           true,
   160  		},
   161  		"deny": {
   162  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   163  			totalWeight:     policy.Threshold.Add(sdk.OneDec()),
   164  			tally:           foundation.NewTallyResult(sdk.ZeroDec(), sdk.OneDec(), sdk.OneDec(), sdk.ZeroDec()),
   165  			valid:           true,
   166  			final:           true,
   167  		},
   168  		"too early": {
   169  			sinceSubmission: policy.Windows.MinExecutionPeriod - time.Nanosecond,
   170  			totalWeight:     policy.Threshold,
   171  			tally:           foundation.NewTallyResult(policy.Threshold, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   172  		},
   173  	}
   174  
   175  	for name, tc := range testCases {
   176  		t.Run(name, func(t *testing.T) {
   177  			result, err := policy.Allow(tc.tally, tc.totalWeight, tc.sinceSubmission)
   178  			if !tc.valid {
   179  				require.Error(t, err)
   180  				return
   181  			}
   182  			require.NoError(t, err)
   183  
   184  			require.Equal(t, tc.final, result.Final)
   185  			if tc.final {
   186  				require.Equal(t, tc.allow, result.Allow)
   187  			}
   188  		})
   189  	}
   190  }
   191  
   192  func TestPercentageDecisionPolicy(t *testing.T) {
   193  	config := foundation.DefaultConfig()
   194  
   195  	testCases := map[string]struct {
   196  		percentage         sdk.Dec
   197  		votingPeriod       time.Duration
   198  		minExecutionPeriod time.Duration
   199  		totalWeight        sdk.Dec
   200  		validBasic         bool
   201  		valid              bool
   202  	}{
   203  		"valid policy": {
   204  			percentage:         sdk.OneDec(),
   205  			votingPeriod:       time.Hour,
   206  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour - time.Nanosecond,
   207  			totalWeight:        sdk.OneDec(),
   208  			validBasic:         true,
   209  			valid:              true,
   210  		},
   211  		"invalid percentage": {
   212  			votingPeriod:       time.Hour,
   213  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour - time.Nanosecond,
   214  			totalWeight:        sdk.OneDec(),
   215  		},
   216  		"invalid voting period": {
   217  			percentage:         sdk.OneDec(),
   218  			minExecutionPeriod: config.MaxExecutionPeriod - time.Nanosecond,
   219  			totalWeight:        sdk.OneDec(),
   220  		},
   221  		"invalid min execution period": {
   222  			percentage:         sdk.OneDec(),
   223  			votingPeriod:       time.Hour,
   224  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour,
   225  			totalWeight:        sdk.OneDec(),
   226  			validBasic:         true,
   227  		},
   228  		"invalid total weight": {
   229  			percentage:         sdk.OneDec(),
   230  			votingPeriod:       time.Hour,
   231  			minExecutionPeriod: config.MaxExecutionPeriod + time.Hour - time.Nanosecond,
   232  			totalWeight:        sdk.ZeroDec(),
   233  			validBasic:         true,
   234  		},
   235  	}
   236  
   237  	for name, tc := range testCases {
   238  		t.Run(name, func(t *testing.T) {
   239  			policy := foundation.PercentageDecisionPolicy{
   240  				Percentage: tc.percentage,
   241  				Windows: &foundation.DecisionPolicyWindows{
   242  					VotingPeriod:       tc.votingPeriod,
   243  					MinExecutionPeriod: tc.minExecutionPeriod,
   244  				},
   245  			}
   246  			require.Equal(t, tc.votingPeriod, policy.GetVotingPeriod())
   247  
   248  			err := policy.ValidateBasic()
   249  			if !tc.validBasic {
   250  				require.Error(t, err)
   251  				return
   252  			}
   253  			require.NoError(t, err)
   254  
   255  			info := foundation.FoundationInfo{
   256  				TotalWeight: tc.totalWeight,
   257  			}
   258  			err = policy.Validate(info, config)
   259  			if !tc.valid {
   260  				require.Error(t, err)
   261  				return
   262  			}
   263  			require.NoError(t, err)
   264  		})
   265  	}
   266  }
   267  
   268  func TestPercentageDecisionPolicyAllow(t *testing.T) {
   269  	config := foundation.DefaultConfig()
   270  	policy := foundation.PercentageDecisionPolicy{
   271  		Percentage: sdk.MustNewDecFromStr("0.8"),
   272  		Windows: &foundation.DecisionPolicyWindows{
   273  			VotingPeriod: time.Hour,
   274  		},
   275  	}
   276  	require.NoError(t, policy.ValidateBasic())
   277  
   278  	info := foundation.FoundationInfo{
   279  		TotalWeight: sdk.OneDec(),
   280  	}
   281  	require.NoError(t, policy.Validate(info, config))
   282  	require.Equal(t, time.Hour, policy.GetVotingPeriod())
   283  
   284  	totalWeight := sdk.NewDec(10)
   285  	testCases := map[string]struct {
   286  		sinceSubmission time.Duration
   287  		tally           foundation.TallyResult
   288  		valid           bool
   289  		final           bool
   290  		allow           bool
   291  	}{
   292  		"allow": {
   293  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   294  			tally:           foundation.NewTallyResult(sdk.NewDec(8), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   295  			valid:           true,
   296  			final:           true,
   297  			allow:           true,
   298  		},
   299  		"allow (abstain)": {
   300  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   301  			tally:           foundation.NewTallyResult(sdk.NewDec(4), sdk.NewDec(5), sdk.ZeroDec(), sdk.ZeroDec()),
   302  			valid:           true,
   303  			final:           true,
   304  			allow:           true,
   305  		},
   306  		"not final": {
   307  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   308  			tally:           foundation.NewTallyResult(sdk.ZeroDec(), sdk.NewDec(5), sdk.NewDec(1), sdk.ZeroDec()),
   309  			valid:           true,
   310  		},
   311  		"deny": {
   312  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   313  			tally:           foundation.NewTallyResult(sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDec(3), sdk.ZeroDec()),
   314  			valid:           true,
   315  			final:           true,
   316  		},
   317  		"deny (all abstain)": {
   318  			sinceSubmission: policy.Windows.MinExecutionPeriod,
   319  			tally:           foundation.NewTallyResult(sdk.ZeroDec(), sdk.NewDec(10), sdk.ZeroDec(), sdk.ZeroDec()),
   320  			valid:           true,
   321  			final:           true,
   322  		},
   323  		"too early": {
   324  			sinceSubmission: policy.Windows.MinExecutionPeriod - time.Nanosecond,
   325  			tally:           foundation.NewTallyResult(sdk.NewDec(8), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   326  		},
   327  	}
   328  
   329  	for name, tc := range testCases {
   330  		t.Run(name, func(t *testing.T) {
   331  			result, err := policy.Allow(tc.tally, totalWeight, tc.sinceSubmission)
   332  			if !tc.valid {
   333  				require.Error(t, err)
   334  				return
   335  			}
   336  			require.NoError(t, err)
   337  
   338  			require.Equal(t, tc.final, result.Final)
   339  			if tc.final {
   340  				require.Equal(t, tc.allow, result.Allow)
   341  			}
   342  		})
   343  	}
   344  }
   345  
   346  func TestMembers(t *testing.T) {
   347  	addrs := make([]sdk.AccAddress, 2)
   348  	for i := range addrs {
   349  		addrs[i] = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
   350  	}
   351  
   352  	testCases := map[string]struct {
   353  		members []foundation.Member
   354  		valid   bool
   355  	}{
   356  		"valid updates": {
   357  			members: []foundation.Member{
   358  				{
   359  					Address: addrs[0].String(),
   360  				},
   361  				{
   362  					Address: addrs[1].String(),
   363  				},
   364  			},
   365  			valid: true,
   366  		},
   367  		"invalid member": {
   368  			members: []foundation.Member{{}},
   369  		},
   370  		"duplicate members": {
   371  			members: []foundation.Member{
   372  				{
   373  					Address: addrs[0].String(),
   374  				},
   375  				{
   376  					Address: addrs[0].String(),
   377  				},
   378  			},
   379  		},
   380  	}
   381  
   382  	for name, tc := range testCases {
   383  		t.Run(name, func(t *testing.T) {
   384  			members := foundation.Members{tc.members}
   385  			err := members.ValidateBasic()
   386  			if !tc.valid {
   387  				require.Error(t, err)
   388  				return
   389  			}
   390  			require.NoError(t, err)
   391  		})
   392  	}
   393  }
   394  
   395  func TestMemberRequests(t *testing.T) {
   396  	addrs := make([]sdk.AccAddress, 2)
   397  	for i := range addrs {
   398  		addrs[i] = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
   399  	}
   400  
   401  	testCases := map[string]struct {
   402  		members []foundation.MemberRequest
   403  		valid   bool
   404  	}{
   405  		"valid requests": {
   406  			members: []foundation.MemberRequest{
   407  				{
   408  					Address: addrs[0].String(),
   409  				},
   410  				{
   411  					Address: addrs[1].String(),
   412  					Remove:  true,
   413  				},
   414  			},
   415  			valid: true,
   416  		},
   417  		"invalid member": {
   418  			members: []foundation.MemberRequest{{}},
   419  		},
   420  		"duplicate requests": {
   421  			members: []foundation.MemberRequest{
   422  				{
   423  					Address: addrs[0].String(),
   424  				},
   425  				{
   426  					Address: addrs[0].String(),
   427  					Remove:  true,
   428  				},
   429  			},
   430  		},
   431  	}
   432  
   433  	for name, tc := range testCases {
   434  		t.Run(name, func(t *testing.T) {
   435  			requests := foundation.MemberRequests{tc.members}
   436  			err := requests.ValidateBasic()
   437  			if !tc.valid {
   438  				require.Error(t, err)
   439  				return
   440  			}
   441  			require.NoError(t, err)
   442  		})
   443  	}
   444  }
   445  
   446  func TestProposal(t *testing.T) {
   447  	addrs := make([]sdk.AccAddress, 4)
   448  	for i := range addrs {
   449  		addrs[i] = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
   450  	}
   451  
   452  	testCases := map[string]struct {
   453  		id        uint64
   454  		proposers []string
   455  		version   uint64
   456  		msgs      []sdk.Msg
   457  		valid     bool
   458  	}{
   459  		"valid proposal": {
   460  			id: 1,
   461  			proposers: []string{
   462  				addrs[0].String(),
   463  				addrs[1].String(),
   464  			},
   465  			version: 1,
   466  			msgs: []sdk.Msg{
   467  				testdata.NewTestMsg(),
   468  			},
   469  			valid: true,
   470  		},
   471  		"invalid id": {
   472  			proposers: []string{
   473  				addrs[0].String(),
   474  				addrs[1].String(),
   475  			},
   476  			version: 1,
   477  			msgs: []sdk.Msg{
   478  				testdata.NewTestMsg(),
   479  			},
   480  		},
   481  		"empty proposers": {
   482  			id:      1,
   483  			version: 1,
   484  			msgs: []sdk.Msg{
   485  				testdata.NewTestMsg(),
   486  			},
   487  		},
   488  		"invalid proposer": {
   489  			id:        1,
   490  			proposers: []string{""},
   491  			version:   1,
   492  			msgs: []sdk.Msg{
   493  				testdata.NewTestMsg(),
   494  			},
   495  		},
   496  		"duplicate proposers": {
   497  			id: 1,
   498  			proposers: []string{
   499  				addrs[0].String(),
   500  				addrs[0].String(),
   501  			},
   502  			version: 1,
   503  			msgs: []sdk.Msg{
   504  				testdata.NewTestMsg(),
   505  			},
   506  		},
   507  		"invalid version": {
   508  			id: 1,
   509  			proposers: []string{
   510  				addrs[0].String(),
   511  				addrs[1].String(),
   512  			},
   513  			msgs: []sdk.Msg{
   514  				testdata.NewTestMsg(),
   515  			},
   516  		},
   517  		"empty msgs": {
   518  			id: 1,
   519  			proposers: []string{
   520  				addrs[0].String(),
   521  				addrs[1].String(),
   522  			},
   523  			version: 1,
   524  		},
   525  		"invalid msg": {
   526  			id: 1,
   527  			proposers: []string{
   528  				addrs[0].String(),
   529  				addrs[1].String(),
   530  			},
   531  			version: 1,
   532  			msgs: []sdk.Msg{
   533  				&foundation.MsgWithdrawFromTreasury{},
   534  			},
   535  		},
   536  	}
   537  
   538  	for name, tc := range testCases {
   539  		t.Run(name, func(t *testing.T) {
   540  			proposal := foundation.Proposal{
   541  				Id:                tc.id,
   542  				Proposers:         tc.proposers,
   543  				FoundationVersion: tc.version,
   544  			}.WithMsgs(tc.msgs)
   545  			require.NotNil(t, proposal)
   546  
   547  			err := proposal.ValidateBasic()
   548  			if !tc.valid {
   549  				require.Error(t, err)
   550  				return
   551  			}
   552  			require.NoError(t, err)
   553  		})
   554  	}
   555  }
   556  
   557  func TestOutsourcingDecisionPolicy(t *testing.T) {
   558  	config := foundation.DefaultConfig()
   559  
   560  	testCases := map[string]struct {
   561  		totalWeight sdk.Dec
   562  		validBasic  bool
   563  		valid       bool
   564  	}{
   565  		"invalid policy": {
   566  			totalWeight: sdk.OneDec(),
   567  			validBasic:  true,
   568  		},
   569  	}
   570  
   571  	for name, tc := range testCases {
   572  		t.Run(name, func(t *testing.T) {
   573  			policy := foundation.OutsourcingDecisionPolicy{}
   574  			require.Zero(t, policy.GetVotingPeriod())
   575  
   576  			err := policy.ValidateBasic()
   577  			if !tc.validBasic {
   578  				require.Error(t, err)
   579  				return
   580  			}
   581  			require.NoError(t, err)
   582  
   583  			info := foundation.FoundationInfo{
   584  				TotalWeight: tc.totalWeight,
   585  			}
   586  			err = policy.Validate(info, config)
   587  			if !tc.valid {
   588  				require.Error(t, err)
   589  				return
   590  			}
   591  			require.NoError(t, err)
   592  		})
   593  	}
   594  }
   595  
   596  func TestOutsourcingDecisionPolicyAllow(t *testing.T) {
   597  	config := foundation.DefaultConfig()
   598  	policy := foundation.OutsourcingDecisionPolicy{}
   599  	require.NoError(t, policy.ValidateBasic())
   600  
   601  	info := foundation.FoundationInfo{
   602  		TotalWeight: sdk.OneDec(),
   603  	}
   604  	require.Error(t, policy.Validate(info, config))
   605  	require.Zero(t, policy.GetVotingPeriod())
   606  
   607  	testCases := map[string]struct {
   608  		sinceSubmission time.Duration
   609  		totalWeight     sdk.Dec
   610  		tally           foundation.TallyResult
   611  		valid           bool
   612  		final           bool
   613  		allow           bool
   614  	}{
   615  		"deny": {
   616  			sinceSubmission: 0,
   617  			totalWeight:     sdk.OneDec(),
   618  			tally:           foundation.NewTallyResult(sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
   619  		},
   620  	}
   621  
   622  	for name, tc := range testCases {
   623  		t.Run(name, func(t *testing.T) {
   624  			result, err := policy.Allow(tc.tally, tc.totalWeight, tc.sinceSubmission)
   625  			if !tc.valid {
   626  				require.Error(t, err)
   627  				return
   628  			}
   629  			require.NoError(t, err)
   630  
   631  			require.Equal(t, tc.final, result.Final)
   632  			if tc.final {
   633  				require.Equal(t, tc.allow, result.Allow)
   634  			}
   635  		})
   636  	}
   637  }