diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml index 63418f12ae22..599b3622034c 100644 --- a/.github/workflows/lint-pr.yml +++ b/.github/workflows/lint-pr.yml @@ -11,6 +11,6 @@ jobs: main: runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v3.4.6 + - uses: amannn/action-semantic-pull-request@v3.5.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/cosmovisor/go.mod b/cosmovisor/go.mod index e2cfe022532a..bc20fa0dc7ee 100644 --- a/cosmovisor/go.mod +++ b/cosmovisor/go.mod @@ -7,7 +7,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.5.9 github.com/otiai10/copy v1.7.0 - github.com/rs/zerolog v1.26.0 + github.com/rs/zerolog v1.26.1 github.com/stretchr/testify v1.7.0 google.golang.org/api v0.44.0 // indirect google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect @@ -109,7 +109,7 @@ require ( github.com/zondax/hid v0.9.0 // indirect go.etcd.io/bbolt v1.3.5 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect diff --git a/cosmovisor/go.sum b/cosmovisor/go.sum index 3ae327cce909..ac21164daf8d 100644 --- a/cosmovisor/go.sum +++ b/cosmovisor/go.sum @@ -731,8 +731,8 @@ github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubr github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= -github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= -github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -898,8 +898,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/x/group/go.sum b/x/group/go.sum index a22e7764a20e..fedb3b4684a4 100644 --- a/x/group/go.sum +++ b/x/group/go.sum @@ -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= diff --git a/x/group/keeper/invariants.go b/x/group/keeper/invariants.go new file mode 100644 index 000000000000..376b91603668 --- /dev/null +++ b/x/group/keeper/invariants.go @@ -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 +} diff --git a/x/group/keeper/invariants_test.go b/x/group/keeper/invariants_test.go new file mode 100644 index 000000000000..05f118e01839 --- /dev/null +++ b/x/group/keeper/invariants_test.go @@ -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) + } +} diff --git a/x/group/keeper/keeper_test.go b/x/group/keeper/keeper_test.go index c631f8c45242..7f7583fec4de 100644 --- a/x/group/keeper/keeper_test.go +++ b/x/group/keeper/keeper_test.go @@ -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{ diff --git a/x/group/keeper/msg_server.go b/x/group/keeper/msg_server.go index e031c1610e06..18150ecb326b 100644 --- a/x/group/keeper/msg_server.go +++ b/x/group/keeper/msg_server.go @@ -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") diff --git a/x/group/module/module.go b/x/group/module/module.go index ffb1e8ad66a0..58c9d893fc01 100644 --- a/x/group/module/module.go +++ b/x/group/module/module.go @@ -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 {