feat: Add invariants to group module (#10621)
## Description Closes: #9898 This PR adds invariants to group module --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
29fe4ecbbc
commit
59ae402537
|
@ -311,7 +311,6 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM
|
|||
github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M=
|
||||
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
||||
|
@ -524,8 +523,6 @@ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9
|
|||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs=
|
||||
github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
|
@ -728,7 +725,6 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
|
|||
github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
|
||||
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
|
||||
github.com/jhump/protoreflect v1.10.1 h1:iH+UZfsbRE6vpyZH7asAjTPWJf7RJbpZ9j/N3lDlKs0=
|
||||
github.com/jhump/protoreflect v1.10.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
|
||||
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
|
@ -953,7 +949,6 @@ github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C
|
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8BfaIeMzgBNKvc=
|
||||
github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ=
|
||||
github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
|
||||
github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
|
@ -1634,7 +1629,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk=
|
||||
|
@ -1724,7 +1718,6 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY
|
|||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
@ -1733,7 +1726,6 @@ golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roY
|
|||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
|
@ -1917,7 +1909,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/group"
|
||||
"github.com/cosmos/cosmos-sdk/x/group/errors"
|
||||
groupmath "github.com/cosmos/cosmos-sdk/x/group/internal/math"
|
||||
"github.com/cosmos/cosmos-sdk/x/group/internal/orm"
|
||||
)
|
||||
|
||||
const (
|
||||
votesInvariant = "Tally-Votes"
|
||||
weightInvariant = "Group-TotalWeight"
|
||||
votesSumInvariant = "Tally-Votes-Sum"
|
||||
)
|
||||
|
||||
// RegisterInvariants registers all group invariants
|
||||
func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) {
|
||||
ir.RegisterRoute(group.ModuleName, votesInvariant, TallyVotesInvariant(keeper))
|
||||
ir.RegisterRoute(group.ModuleName, weightInvariant, GroupTotalWeightInvariant(keeper))
|
||||
ir.RegisterRoute(group.ModuleName, votesSumInvariant, TallyVotesSumInvariant(keeper))
|
||||
}
|
||||
|
||||
// TallyVotesInvariant checks that vote tally sums must never have less than the block before.
|
||||
func TallyVotesInvariant(keeper Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
if ctx.BlockHeight()-1 < 0 {
|
||||
return sdk.FormatInvariant(group.ModuleName, votesInvariant, "Not enough blocks to perform TallyVotesInvariant"), false
|
||||
}
|
||||
prevCtx, _ := ctx.CacheContext()
|
||||
prevCtx = prevCtx.WithBlockHeight(ctx.BlockHeight() - 1)
|
||||
msg, broken := TallyVotesInvariantHelper(ctx, prevCtx, keeper.key, keeper.proposalTable)
|
||||
return sdk.FormatInvariant(group.ModuleName, votesInvariant, msg), broken
|
||||
}
|
||||
}
|
||||
|
||||
// GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members.
|
||||
func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
msg, broken := GroupTotalWeightInvariantHelper(ctx, keeper.key, keeper.groupTable, keeper.groupMemberByGroupIndex)
|
||||
return sdk.FormatInvariant(group.ModuleName, weightInvariant, msg), broken
|
||||
}
|
||||
}
|
||||
|
||||
// TallyVotesSumInvariant checks that proposal VoteState must correspond to the vote choice.
|
||||
func TallyVotesSumInvariant(keeper Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.groupTable, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupAccountTable)
|
||||
return sdk.FormatInvariant(group.ModuleName, votesSumInvariant, msg), broken
|
||||
}
|
||||
}
|
||||
|
||||
func TallyVotesInvariantHelper(ctx sdk.Context, prevCtx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table) (string, bool) {
|
||||
|
||||
var msg string
|
||||
var broken bool
|
||||
|
||||
prevIt, err := proposalTable.PrefixScan(prevCtx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
curIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
var curProposals []*group.Proposal
|
||||
_, err = orm.ReadAll(curIt, &curProposals)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
var prevProposals []*group.Proposal
|
||||
_, err = orm.ReadAll(prevIt, &prevProposals)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
for i := 0; i < len(prevProposals); i++ {
|
||||
if prevProposals[i].ProposalId == curProposals[i].ProposalId {
|
||||
prevYesCount, err := prevProposals[i].VoteState.GetYesCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curYesCount, err := curProposals[i].VoteState.GetYesCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
prevNoCount, err := prevProposals[i].VoteState.GetNoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curNoCount, err := curProposals[i].VoteState.GetNoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
prevAbstainCount, err := prevProposals[i].VoteState.GetAbstainCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curAbstainCount, err := curProposals[i].VoteState.GetAbstainCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
prevVetoCount, err := prevProposals[i].VoteState.GetVetoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
curVetoCount, err := curProposals[i].VoteState.GetVetoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
|
||||
return msg, broken
|
||||
}
|
||||
if (curYesCount.Cmp(prevYesCount) == -1) || (curNoCount.Cmp(prevNoCount) == -1) || (curAbstainCount.Cmp(prevAbstainCount) == -1) || (curVetoCount.Cmp(prevVetoCount) == -1) {
|
||||
broken = true
|
||||
msg += "vote tally sums must never have less than the block before\n"
|
||||
return msg, broken
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) {
|
||||
|
||||
var msg string
|
||||
var broken bool
|
||||
|
||||
var groupInfo group.GroupInfo
|
||||
var groupMember group.GroupMember
|
||||
|
||||
groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
defer groupIt.Close()
|
||||
|
||||
for {
|
||||
membersWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
_, err = groupIt.LoadNext(&groupInfo)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
memIt, err := groupMemberByGroupIndex.Get(ctx.KVStore(key), groupInfo.GroupId)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while returning group member iterator for group with ID %d\n%v\n", groupInfo.GroupId, err)
|
||||
return msg, broken
|
||||
}
|
||||
defer memIt.Close()
|
||||
|
||||
for {
|
||||
_, err = memIt.LoadNext(&groupMember)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
curMemWeight, err := groupmath.NewNonNegativeDecFromString(groupMember.GetMember().GetWeight())
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing non-nengative decimal for group member %s\n%v\n", groupMember.Member.Address, err)
|
||||
return msg, broken
|
||||
}
|
||||
membersWeight, err = groupmath.Add(membersWeight, curMemWeight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("decimal addition error while adding group member voting weight to total voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
}
|
||||
groupWeight, err := groupmath.NewNonNegativeDecFromString(groupInfo.GetTotalWeight())
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing non-nengative decimal for group with ID %d\n%v\n", groupInfo.GroupId, err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
if groupWeight.Cmp(membersWeight) != 0 {
|
||||
broken = true
|
||||
msg += fmt.Sprintf("group's TotalWeight must be equal to the sum of its members' weights\ngroup weight: %s\nSum of group members weights: %s\n", groupWeight.String(), membersWeight.String())
|
||||
break
|
||||
}
|
||||
}
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupAccountTable orm.PrimaryKeyTable) (string, bool) {
|
||||
var msg string
|
||||
var broken bool
|
||||
|
||||
var groupInfo group.GroupInfo
|
||||
var proposal group.Proposal
|
||||
var groupAcc group.GroupAccountInfo
|
||||
var groupMem group.GroupMember
|
||||
var vote group.Vote
|
||||
|
||||
proposalIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
defer proposalIt.Close()
|
||||
|
||||
for {
|
||||
|
||||
totalVotingWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing positive dec zero for total voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
yesVoteWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing positive dec zero for yes voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
noVoteWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing positive dec zero for no voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
abstainVoteWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing positive dec zero for abstain voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
vetoVoteWeight, err := groupmath.NewNonNegativeDecFromString("0")
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing positive dec zero for veto voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
_, err = proposalIt.LoadNext(&proposal)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
|
||||
err = groupAccountTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupAccountInfo{Address: proposal.Address}), &groupAcc)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("group account not found for address: %s\n%v\n", proposal.Address, err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
if proposal.GroupAccountVersion != groupAcc.Version {
|
||||
msg += fmt.Sprintf("group account with address %s was modified\n", groupAcc.Address)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
_, err = groupTable.GetOne(ctx.KVStore(key), groupAcc.GroupId, &groupInfo)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("group info not found for group id %d\n%v\n", groupAcc.GroupId, err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
if groupInfo.Version != proposal.GroupVersion {
|
||||
msg += fmt.Sprintf("group with id %d was modified\n", groupInfo.GroupId)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
voteIt, err := voteByProposalIndex.Get(ctx.KVStore(key), proposal.ProposalId)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.ProposalId, err)
|
||||
return msg, broken
|
||||
}
|
||||
defer voteIt.Close()
|
||||
|
||||
for {
|
||||
_, err := voteIt.LoadNext(&vote)
|
||||
if errors.ErrORMIteratorDone.Is(err) {
|
||||
break
|
||||
}
|
||||
|
||||
err = groupMemberTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupMember{GroupId: groupAcc.GroupId, Member: &group.Member{Address: vote.Voter}}), &groupMem)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("group member not found with group ID %d and group member %s\n%v\n", groupAcc.GroupId, vote.Voter, err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
curMemVotingWeight, err := groupmath.NewNonNegativeDecFromString(groupMem.Member.Weight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while parsing non-negative decimal for group member %s\n%v\n", groupMem.Member.Address, err)
|
||||
return msg, broken
|
||||
}
|
||||
totalVotingWeight, err = groupmath.Add(totalVotingWeight, curMemVotingWeight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("decimal addition error while adding current member voting weight to total voting weight\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
switch vote.Choice {
|
||||
case group.Choice_CHOICE_YES:
|
||||
yesVoteWeight, err = groupmath.Add(yesVoteWeight, curMemVotingWeight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("decimal addition error\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
case group.Choice_CHOICE_NO:
|
||||
noVoteWeight, err = groupmath.Add(noVoteWeight, curMemVotingWeight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("decimal addition error\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
case group.Choice_CHOICE_ABSTAIN:
|
||||
abstainVoteWeight, err = groupmath.Add(abstainVoteWeight, curMemVotingWeight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("decimal addition error\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
case group.Choice_CHOICE_VETO:
|
||||
vetoVoteWeight, err = groupmath.Add(vetoVoteWeight, curMemVotingWeight)
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("decimal addition error\n%v\n", err)
|
||||
return msg, broken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalProposalVotes, err := proposal.VoteState.TotalCounts()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting total weighted votes of proposal with ID %d\n%v\n", proposal.ProposalId, err)
|
||||
return msg, broken
|
||||
}
|
||||
proposalYesCount, err := proposal.VoteState.GetYesCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting the weighted sum of yes votes for proposal with ID %d\n%v\n", proposal.ProposalId, err)
|
||||
return msg, broken
|
||||
}
|
||||
proposalNoCount, err := proposal.VoteState.GetNoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting the weighted sum of no votes for proposal with ID %d\n%v\n", proposal.ProposalId, err)
|
||||
return msg, broken
|
||||
}
|
||||
proposalAbstainCount, err := proposal.VoteState.GetAbstainCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting the weighted sum of abstain votes for proposal with ID %d\n%v\n", proposal.ProposalId, err)
|
||||
return msg, broken
|
||||
}
|
||||
proposalVetoCount, err := proposal.VoteState.GetVetoCount()
|
||||
if err != nil {
|
||||
msg += fmt.Sprintf("error while getting the weighted sum of veto votes for proposal with ID %d\n%v\n", proposal.ProposalId, err)
|
||||
return msg, broken
|
||||
}
|
||||
|
||||
if totalProposalVotes.Cmp(totalVotingWeight) != 0 {
|
||||
broken = true
|
||||
msg += fmt.Sprintf("proposal VoteState must correspond to the sum of votes weights\nProposal with ID %d has total proposal votes %s, but got sum of votes weights %s\n", proposal.ProposalId, totalProposalVotes.String(), totalVotingWeight.String())
|
||||
break
|
||||
}
|
||||
|
||||
if (yesVoteWeight.Cmp(proposalYesCount) != 0) || (noVoteWeight.Cmp(proposalNoCount) != 0) || (abstainVoteWeight.Cmp(proposalAbstainCount) != 0) || (vetoVoteWeight.Cmp(proposalVetoCount) != 0) {
|
||||
broken = true
|
||||
msg += fmt.Sprintf("proposal VoteState must correspond to the vote choice\nProposal with ID %d and voter address %s must correspond to the vote choice\n", proposal.ProposalId, vote.Voter)
|
||||
break
|
||||
}
|
||||
}
|
||||
return msg, broken
|
||||
}
|
|
@ -0,0 +1,585 @@
|
|||
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{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "1", NoCount: "0", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
|
||||
curProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "2", NoCount: "0", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
},
|
||||
"current block yes vote count must be greater than previous block yes vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "2", NoCount: "0", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "1", NoCount: "0", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"current block no vote count must be greater than previous block no vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "0", NoCount: "2", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "0", NoCount: "1", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"current block abstain vote count must be greater than previous block abstain vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "0", NoCount: "0", AbstainCount: "2", VetoCount: "0"},
|
||||
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "0", NoCount: "0", AbstainCount: "1", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"current block veto vote count must be greater than previous block veto vote count": {
|
||||
prevProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: prevCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "0", NoCount: "0", AbstainCount: "0", VetoCount: "2"},
|
||||
Timeout: prevCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
curProposal: &group.Proposal{
|
||||
ProposalId: 1,
|
||||
Address: addr2.String(),
|
||||
Proposers: []string{addr2.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "0", NoCount: "0", AbstainCount: "0", VetoCount: "1"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
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{
|
||||
GroupId: 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{
|
||||
GroupId: 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 Account Table
|
||||
groupAccountTable, err := orm.NewPrimaryKeyTable([2]byte{keeper.GroupAccountTablePrefix}, &group.GroupAccountInfo{}, 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
|
||||
groupAcc *group.GroupAccountInfo
|
||||
groupMembers []*group.GroupMember
|
||||
proposal *group.Proposal
|
||||
votes []*group.Vote
|
||||
expBroken bool
|
||||
}{
|
||||
"invariant not broken": {
|
||||
groupsInfo: &group.GroupInfo{
|
||||
GroupId: 1,
|
||||
Admin: adminAddr.String(),
|
||||
Version: 1,
|
||||
TotalWeight: "7",
|
||||
},
|
||||
groupAcc: &group.GroupAccountInfo{
|
||||
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{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "4", NoCount: "3", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr1.String(),
|
||||
Choice: group.Choice_CHOICE_YES,
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
},
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr2.String(),
|
||||
Choice: group.Choice_CHOICE_NO,
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
},
|
||||
},
|
||||
expBroken: false,
|
||||
},
|
||||
"proposal tally must correspond to the sum of vote weights": {
|
||||
groupsInfo: &group.GroupInfo{
|
||||
GroupId: 1,
|
||||
Admin: adminAddr.String(),
|
||||
Version: 1,
|
||||
TotalWeight: "5",
|
||||
},
|
||||
groupAcc: &group.GroupAccountInfo{
|
||||
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{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "6", NoCount: "0", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr1.String(),
|
||||
Choice: group.Choice_CHOICE_YES,
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
},
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr2.String(),
|
||||
Choice: group.Choice_CHOICE_YES,
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
},
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
"proposal VoteState must correspond to the vote choice": {
|
||||
groupsInfo: &group.GroupInfo{
|
||||
GroupId: 1,
|
||||
Admin: adminAddr.String(),
|
||||
Version: 1,
|
||||
TotalWeight: "7",
|
||||
},
|
||||
groupAcc: &group.GroupAccountInfo{
|
||||
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{
|
||||
ProposalId: 1,
|
||||
Address: addr1.String(),
|
||||
Proposers: []string{addr1.String()},
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
GroupVersion: 1,
|
||||
GroupAccountVersion: 1,
|
||||
Status: group.ProposalStatusSubmitted,
|
||||
Result: group.ProposalResultUnfinalized,
|
||||
VoteState: group.Tally{YesCount: "4", NoCount: "3", AbstainCount: "0", VetoCount: "0"},
|
||||
Timeout: curCtx.BlockTime().Add(time.Second * 600),
|
||||
ExecutorResult: group.ProposalExecutorResultNotRun,
|
||||
},
|
||||
votes: []*group.Vote{
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr1.String(),
|
||||
Choice: group.Choice_CHOICE_YES,
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
},
|
||||
{
|
||||
ProposalId: 1,
|
||||
Voter: addr2.String(),
|
||||
Choice: group.Choice_CHOICE_ABSTAIN,
|
||||
SubmittedAt: curCtx.BlockTime(),
|
||||
},
|
||||
},
|
||||
expBroken: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
cacheCurCtx, _ := curCtx.CacheContext()
|
||||
groupsInfo := spec.groupsInfo
|
||||
proposal := spec.proposal
|
||||
groupAcc := spec.groupAcc
|
||||
groupMembers := spec.groupMembers
|
||||
votes := spec.votes
|
||||
|
||||
_, err := groupTable.Create(cacheCurCtx.KVStore(key), groupsInfo)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = groupAcc.SetDecisionPolicy(group.NewThresholdDecisionPolicy("1", time.Second))
|
||||
s.Require().NoError(err)
|
||||
err = groupAccountTable.Create(cacheCurCtx.KVStore(key), groupAcc)
|
||||
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, *groupAccountTable)
|
||||
s.Require().Equal(spec.expBroken, broken)
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
gogotypes "github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
tmtime "github.com/tendermint/tendermint/libs/time"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
|
@ -1408,12 +1407,7 @@ func (s *TestSuite) TestVote() {
|
|||
s.Assert().Equal(req.Address, proposals[0].Address)
|
||||
s.Assert().Equal(req.Metadata, proposals[0].Metadata)
|
||||
s.Assert().Equal(req.Proposers, proposals[0].Proposers)
|
||||
|
||||
psubmittedAt, err := gogotypes.TimestampProto(proposals[0].SubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
submittedAt, err := gogotypes.TimestampFromProto(psubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
s.Assert().Equal(s.blockTime, submittedAt)
|
||||
s.Assert().Equal(s.blockTime, proposals[0].SubmittedAt)
|
||||
|
||||
s.Assert().Equal(uint64(1), proposals[0].GroupVersion)
|
||||
s.Assert().Equal(uint64(1), proposals[0].GroupAccountVersion)
|
||||
|
@ -1751,11 +1745,7 @@ func (s *TestSuite) TestVote() {
|
|||
s.Assert().Equal(spec.req.Voter, loaded.Voter)
|
||||
s.Assert().Equal(spec.req.Choice, loaded.Choice)
|
||||
s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
|
||||
lsubmittedAt, err := gogotypes.TimestampProto(loaded.SubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
submittedAt, err := gogotypes.TimestampFromProto(lsubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
s.Assert().Equal(s.blockTime, submittedAt)
|
||||
s.Assert().Equal(s.blockTime, loaded.SubmittedAt)
|
||||
|
||||
// query votes by proposal
|
||||
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
|
||||
|
@ -1769,11 +1759,7 @@ func (s *TestSuite) TestVote() {
|
|||
s.Assert().Equal(spec.req.Voter, vote.Voter)
|
||||
s.Assert().Equal(spec.req.Choice, vote.Choice)
|
||||
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
|
||||
vsubmittedAt, err := gogotypes.TimestampProto(vote.SubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
submittedAt, err = gogotypes.TimestampFromProto(vsubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
s.Assert().Equal(s.blockTime, submittedAt)
|
||||
s.Assert().Equal(s.blockTime, vote.SubmittedAt)
|
||||
|
||||
// query votes by voter
|
||||
voter := spec.req.Voter
|
||||
|
@ -1787,11 +1773,7 @@ func (s *TestSuite) TestVote() {
|
|||
s.Assert().Equal(voter, votesByVoter[0].Voter)
|
||||
s.Assert().Equal(spec.req.Choice, votesByVoter[0].Choice)
|
||||
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
|
||||
vsubmittedAt, err = gogotypes.TimestampProto(votesByVoter[0].SubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
submittedAt, err = gogotypes.TimestampFromProto(vsubmittedAt)
|
||||
s.Require().NoError(err)
|
||||
s.Assert().Equal(s.blockTime, submittedAt)
|
||||
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmittedAt)
|
||||
|
||||
// and proposal is updated
|
||||
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
|
||||
|
|
|
@ -273,7 +273,6 @@ func (k Keeper) CreateGroupAccount(goCtx context.Context, req *group.MsgCreateGr
|
|||
// handle a rare collision
|
||||
continue
|
||||
}
|
||||
|
||||
acc := k.accKeeper.NewAccount(ctx, &authtypes.ModuleAccount{
|
||||
BaseAccount: &authtypes.BaseAccount{
|
||||
Address: accountAddr.String(),
|
||||
|
@ -405,11 +404,6 @@ func (k Keeper) CreateProposal(goCtx context.Context, req *group.MsgCreatePropos
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// blockTime, err := gogotypes.TimestampProto(ctx.BlockTime())
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(err, "block time conversion")
|
||||
}
|
||||
|
||||
policy := account.GetDecisionPolicy()
|
||||
if policy == nil {
|
||||
return nil, sdkerrors.Wrap(errors.ErrEmpty, "nil policy")
|
||||
|
|
|
@ -103,7 +103,9 @@ func (AppModule) Name() string {
|
|||
}
|
||||
|
||||
// RegisterInvariants does nothing, there are no invariants to enforce
|
||||
func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
|
||||
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
|
||||
groupkeeper.RegisterInvariants(ir, am.keeper)
|
||||
}
|
||||
|
||||
// Deprecated: Route returns the message routing key for the group module.
|
||||
func (am AppModule) Route() sdk.Route {
|
||||
|
|
Loading…
Reference in New Issue