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 }