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 }