cosmos-sdk/x/group/keeper/invariants_test.go

586 lines
19 KiB
Go

package keeper_test
import (
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/store"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/internal/orm"
"github.com/cosmos/cosmos-sdk/x/group/keeper"
)
type invariantTestSuite struct {
suite.Suite
ctx sdk.Context
cdc *codec.ProtoCodec
key *storetypes.KVStoreKey
blockTime time.Time
}
func TestInvariantTestSuite(t *testing.T) {
suite.Run(t, new(invariantTestSuite))
}
func (s *invariantTestSuite) SetupSuite() {
interfaceRegistry := types.NewInterfaceRegistry()
group.RegisterInterfaces(interfaceRegistry)
cdc := codec.NewProtoCodec(interfaceRegistry)
key := sdk.NewKVStoreKey(group.ModuleName)
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db)
_ = cms.LoadLatestVersion()
sdkCtx := sdk.NewContext(cms, tmproto.Header{}, false, log.NewNopLogger())
s.ctx = sdkCtx
s.cdc = cdc
s.key = key
}
func (s *invariantTestSuite) TestTallyVotesInvariant() {
sdkCtx, _ := s.ctx.CacheContext()
curCtx, cdc, key := sdkCtx, s.cdc, s.key
prevCtx, _ := curCtx.CacheContext()
prevCtx = prevCtx.WithBlockHeight(curCtx.BlockHeight() - 1)
// Proposal Table
proposalTable, err := orm.NewAutoUInt64Table([2]byte{keeper.ProposalTablePrefix}, keeper.ProposalTableSeqPrefix, &group.Proposal{}, cdc)
s.Require().NoError(err)
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
specs := map[string]struct {
prevProposal *group.Proposal
curProposal *group.Proposal
expBroken bool
}{
"invariant not broken": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
},
"current block yes vote count must be greater than previous block yes vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
"current block no vote count must be greater than previous block no vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "2", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "1", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
"current block abstain vote count must be greater than previous block abstain vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "2", NoWithVetoCount: "0"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "1", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
"current block veto vote count must be greater than previous block veto vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "2"},
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "1"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
}
for _, spec := range specs {
prevProposal := spec.prevProposal
curProposal := spec.curProposal
cachePrevCtx, _ := prevCtx.CacheContext()
cacheCurCtx, _ := curCtx.CacheContext()
_, err = proposalTable.Create(cachePrevCtx.KVStore(key), prevProposal)
s.Require().NoError(err)
_, err = proposalTable.Create(cacheCurCtx.KVStore(key), curProposal)
s.Require().NoError(err)
_, broken := keeper.TallyVotesInvariantHelper(cacheCurCtx, cachePrevCtx, key, *proposalTable)
s.Require().Equal(spec.expBroken, broken)
}
}
func (s *invariantTestSuite) TestGroupTotalWeightInvariant() {
sdkCtx, _ := s.ctx.CacheContext()
curCtx, cdc, key := sdkCtx, s.cdc, s.key
// Group Table
groupTable, err := orm.NewAutoUInt64Table([2]byte{keeper.GroupTablePrefix}, keeper.GroupTableSeqPrefix, &group.GroupInfo{}, cdc)
s.Require().NoError(err)
// Group Member Table
groupMemberTable, err := orm.NewPrimaryKeyTable([2]byte{keeper.GroupMemberTablePrefix}, &group.GroupMember{}, cdc)
s.Require().NoError(err)
groupMemberByGroupIndex, err := orm.NewIndex(groupMemberTable, keeper.GroupMemberByGroupIndexPrefix, func(val interface{}) ([]interface{}, error) {
group := val.(*group.GroupMember).GroupId
return []interface{}{group}, nil
}, group.GroupMember{}.GroupId)
s.Require().NoError(err)
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
specs := map[string]struct {
groupsInfo *group.GroupInfo
groupMembers []*group.GroupMember
expBroken bool
}{
"invariant not broken": {
groupsInfo: &group.GroupInfo{
Id: 1,
Admin: addr1.String(),
Version: 1,
TotalWeight: "3",
},
groupMembers: []*group.GroupMember{
{
GroupId: 1,
Member: &group.Member{
Address: addr1.String(),
Weight: "1",
},
},
{
GroupId: 1,
Member: &group.Member{
Address: addr2.String(),
Weight: "2",
},
},
},
expBroken: false,
},
"group's TotalWeight must be equal to sum of its members weight ": {
groupsInfo: &group.GroupInfo{
Id: 1,
Admin: addr1.String(),
Version: 1,
TotalWeight: "3",
},
groupMembers: []*group.GroupMember{
{
GroupId: 1,
Member: &group.Member{
Address: addr1.String(),
Weight: "2",
},
},
{
GroupId: 1,
Member: &group.Member{
Address: addr2.String(),
Weight: "2",
},
},
},
expBroken: true,
},
}
for _, spec := range specs {
cacheCurCtx, _ := curCtx.CacheContext()
groupsInfo := spec.groupsInfo
groupMembers := spec.groupMembers
_, err := groupTable.Create(cacheCurCtx.KVStore(key), groupsInfo)
s.Require().NoError(err)
for i := 0; i < len(groupMembers); i++ {
err := groupMemberTable.Create(cacheCurCtx.KVStore(key), groupMembers[i])
s.Require().NoError(err)
}
_, broken := keeper.GroupTotalWeightInvariantHelper(cacheCurCtx, key, *groupTable, groupMemberByGroupIndex)
s.Require().Equal(spec.expBroken, broken)
}
}
func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
sdkCtx, _ := s.ctx.CacheContext()
curCtx, cdc, key := sdkCtx, s.cdc, s.key
// Group Table
groupTable, err := orm.NewAutoUInt64Table([2]byte{keeper.GroupTablePrefix}, keeper.GroupTableSeqPrefix, &group.GroupInfo{}, cdc)
s.Require().NoError(err)
// Group Policy Table
groupPolicyTable, err := orm.NewPrimaryKeyTable([2]byte{keeper.GroupPolicyTablePrefix}, &group.GroupPolicyInfo{}, cdc)
s.Require().NoError(err)
// Group Member Table
groupMemberTable, err := orm.NewPrimaryKeyTable([2]byte{keeper.GroupMemberTablePrefix}, &group.GroupMember{}, cdc)
s.Require().NoError(err)
// Proposal Table
proposalTable, err := orm.NewAutoUInt64Table([2]byte{keeper.ProposalTablePrefix}, keeper.ProposalTableSeqPrefix, &group.Proposal{}, cdc)
s.Require().NoError(err)
// Vote Table
voteTable, err := orm.NewPrimaryKeyTable([2]byte{keeper.VoteTablePrefix}, &group.Vote{}, cdc)
s.Require().NoError(err)
voteByProposalIndex, err := orm.NewIndex(voteTable, keeper.VoteByProposalIndexPrefix, func(value interface{}) ([]interface{}, error) {
return []interface{}{value.(*group.Vote).ProposalId}, nil
}, group.Vote{}.ProposalId)
s.Require().NoError(err)
_, _, adminAddr := testdata.KeyTestPubAddr()
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
specs := map[string]struct {
groupsInfo *group.GroupInfo
groupPolicy *group.GroupPolicyInfo
groupMembers []*group.GroupMember
proposal *group.Proposal
votes []*group.Vote
expBroken bool
}{
"invariant not broken": {
groupsInfo: &group.GroupInfo{
Id: 1,
Admin: adminAddr.String(),
Version: 1,
TotalWeight: "7",
},
groupPolicy: &group.GroupPolicyInfo{
Address: addr1.String(),
GroupId: 1,
Admin: adminAddr.String(),
Version: 1,
},
groupMembers: []*group.GroupMember{
{
GroupId: 1,
Member: &group.Member{
Address: addr1.String(),
Weight: "4",
},
},
{
GroupId: 1,
Member: &group.Member{
Address: addr2.String(),
Weight: "3",
},
},
},
proposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
{
ProposalId: 1,
Voter: addr1.String(),
Option: group.VOTE_OPTION_YES,
SubmitTime: curCtx.BlockTime(),
},
{
ProposalId: 1,
Voter: addr2.String(),
Option: group.VOTE_OPTION_NO,
SubmitTime: curCtx.BlockTime(),
},
},
expBroken: false,
},
"proposal tally must correspond to the sum of vote weights": {
groupsInfo: &group.GroupInfo{
Id: 1,
Admin: adminAddr.String(),
Version: 1,
TotalWeight: "5",
},
groupPolicy: &group.GroupPolicyInfo{
Address: addr1.String(),
GroupId: 1,
Admin: adminAddr.String(),
Version: 1,
},
groupMembers: []*group.GroupMember{
{
GroupId: 1,
Member: &group.Member{
Address: addr1.String(),
Weight: "2",
},
},
{
GroupId: 1,
Member: &group.Member{
Address: addr2.String(),
Weight: "3",
},
},
},
proposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
{
ProposalId: 1,
Voter: addr1.String(),
Option: group.VOTE_OPTION_YES,
SubmitTime: curCtx.BlockTime(),
},
{
ProposalId: 1,
Voter: addr2.String(),
Option: group.VOTE_OPTION_YES,
SubmitTime: curCtx.BlockTime(),
},
},
expBroken: true,
},
"proposal FinalTallyResult must correspond to the vote option": {
groupsInfo: &group.GroupInfo{
Id: 1,
Admin: adminAddr.String(),
Version: 1,
TotalWeight: "7",
},
groupPolicy: &group.GroupPolicyInfo{
Address: addr1.String(),
GroupId: 1,
Admin: adminAddr.String(),
Version: 1,
},
groupMembers: []*group.GroupMember{
{
GroupId: 1,
Member: &group.Member{
Address: addr1.String(),
Weight: "4",
},
},
{
GroupId: 1,
Member: &group.Member{
Address: addr2.String(),
Weight: "3",
},
},
},
proposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
Timeout: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
{
ProposalId: 1,
Voter: addr1.String(),
Option: group.VOTE_OPTION_YES,
SubmitTime: curCtx.BlockTime(),
},
{
ProposalId: 1,
Voter: addr2.String(),
Option: group.VOTE_OPTION_ABSTAIN,
SubmitTime: curCtx.BlockTime(),
},
},
expBroken: true,
},
}
for _, spec := range specs {
cacheCurCtx, _ := curCtx.CacheContext()
groupsInfo := spec.groupsInfo
proposal := spec.proposal
groupPolicy := spec.groupPolicy
groupMembers := spec.groupMembers
votes := spec.votes
_, err := groupTable.Create(cacheCurCtx.KVStore(key), groupsInfo)
s.Require().NoError(err)
err = groupPolicy.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Second))
s.Require().NoError(err)
err = groupPolicyTable.Create(cacheCurCtx.KVStore(key), groupPolicy)
s.Require().NoError(err)
for i := 0; i < len(groupMembers); i++ {
err = groupMemberTable.Create(cacheCurCtx.KVStore(key), groupMembers[i])
s.Require().NoError(err)
}
_, err = proposalTable.Create(cacheCurCtx.KVStore(key), proposal)
s.Require().NoError(err)
for i := 0; i < len(votes); i++ {
err = voteTable.Create(cacheCurCtx.KVStore(key), votes[i])
s.Require().NoError(err)
}
_, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *groupTable, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable)
s.Require().Equal(spec.expBroken, broken)
}
}