package keeper import ( "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/committee/types" ) // RegisterInvariants registers all committee invariants func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { ir.RegisterRoute(types.ModuleName, "valid-committees", ValidCommitteesInvariant(k)) ir.RegisterRoute(types.ModuleName, "valid-proposals", ValidProposalsInvariant(k)) ir.RegisterRoute(types.ModuleName, "valid-votes", ValidVotesInvariant(k)) } // ValidCommitteesInvariant verifies that all committees in the store are independently valid func ValidCommitteesInvariant(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { var validationErr error var invalidCommittee types.Committee k.IterateCommittees(ctx, func(com types.Committee) bool { if err := com.Validate(); err != nil { validationErr = err invalidCommittee = com return true } return false }) broken := validationErr != nil invariantMessage := sdk.FormatInvariant( types.ModuleName, "valid committees", fmt.Sprintf( "\tfound invalid committee, reason: %s\n"+ "\tcommittee:\n\t%+v\n", validationErr, invalidCommittee), ) return invariantMessage, broken } } // ValidProposalsInvariant verifies that all proposals in the store are valid func ValidProposalsInvariant(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { var validationErr error var invalidProposal types.Proposal k.IterateProposals(ctx, func(proposal types.Proposal) bool { invalidProposal = proposal if err := proposal.PubProposal.ValidateBasic(); err != nil { validationErr = err return true } currentTime := ctx.BlockTime() if !currentTime.Equal(time.Time{}) { // this avoids a simulator bug where app.InitGenesis is called with blockTime=0 instead of the correct time if proposal.Deadline.Before(currentTime) { validationErr = fmt.Errorf("deadline after current block time %s", currentTime) return true } } com, found := k.GetCommittee(ctx, proposal.CommitteeID) if !found { validationErr = fmt.Errorf("proposal has no committee %d", proposal.CommitteeID) return true } if !com.HasPermissionsFor(proposal.PubProposal) { validationErr = fmt.Errorf("proposal not permitted for committee %+v", com) return true } return false }) broken := validationErr != nil invariantMessage := sdk.FormatInvariant( types.ModuleName, "valid proposals", fmt.Sprintf( "\tfound invalid proposal, reason: %s\n"+ "\tproposal:\n\t%s\n", validationErr, invalidProposal), ) return invariantMessage, broken } } // ValidVotesInvariant verifies that all votes in the store are valid func ValidVotesInvariant(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { var validationErr error var invalidVote types.Vote k.IterateVotes(ctx, func(vote types.Vote) bool { invalidVote = vote if vote.Voter.Empty() { validationErr = fmt.Errorf("empty voter address") return true } proposal, found := k.GetProposal(ctx, vote.ProposalID) if !found { validationErr = fmt.Errorf("vote has no proposal %d", vote.ProposalID) return true } com, found := k.GetCommittee(ctx, proposal.CommitteeID) if !found { validationErr = fmt.Errorf("vote's proposal has no committee %d", proposal.CommitteeID) return true } if !com.HasMember(vote.Voter) { validationErr = fmt.Errorf("voter is not a member of committee %+v", com) return true } return false }) broken := validationErr != nil invariantMessage := sdk.FormatInvariant( types.ModuleName, "valid votes", fmt.Sprintf( "\tfound invalid vote, reason: %s\n"+ "\tvote:\n\t%+v\n", validationErr, invalidVote), ) return invariantMessage, broken } }