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

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