diff --git a/.pending/breaking/modules/_4665-gov-refactor b/.pending/breaking/modules/_4665-gov-refactor new file mode 100644 index 000000000..a2e6fa548 --- /dev/null +++ b/.pending/breaking/modules/_4665-gov-refactor @@ -0,0 +1,3 @@ +#4665 Refactored `x/gov` module structure and dev-UX: + - Prepare for module spec integration + - Update gov keys to use big endian encoding instead of little endian \ No newline at end of file diff --git a/simapp/app.go b/simapp/app.go index 7858a3f0c..a4193f63d 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -136,7 +136,7 @@ func NewSimApp( mintSubspace := app.paramsKeeper.Subspace(mint.DefaultParamspace) distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) - govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace) + govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) // add keepers @@ -157,7 +157,7 @@ func NewSimApp( govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler). AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)). AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)) - app.govKeeper = gov.NewKeeper(app.cdc, keys[gov.StoreKey], app.paramsKeeper, govSubspace, + app.govKeeper = gov.NewKeeper(app.cdc, keys[gov.StoreKey], govSubspace, app.supplyKeeper, &stakingKeeper, gov.DefaultCodespace, govRouter) // register the staking hooks diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 06215cf7e..b679360d6 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -121,10 +121,10 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[feeCollectorAcc.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true - blacklistedAddrs[distrAcc.String()] = true + blacklistedAddrs[feeCollectorAcc.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true + blacklistedAddrs[distrAcc.GetAddress().String()] = true cdc := MakeTestCodec() pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) diff --git a/x/gov/endblocker.go b/x/gov/abci.go similarity index 95% rename from x/gov/endblocker.go rename to x/gov/abci.go index d9d7933e0..31b1f40c9 100644 --- a/x/gov/endblocker.go +++ b/x/gov/abci.go @@ -39,7 +39,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) { keeper.IterateActiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal Proposal) bool { var tagValue, logMsg string - passes, burnDeposits, tallyResults := tally(ctx, keeper, proposal) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) if burnDeposits { keeper.DeleteDeposits(ctx, proposal.ProposalID) @@ -48,7 +48,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) { } if passes { - handler := keeper.router.GetRoute(proposal.ProposalRoute()) + handler := keeper.Router().GetRoute(proposal.ProposalRoute()) cacheCtx, writeCache := ctx.CacheContext() // The proposal handler may execute state mutating logic depending diff --git a/x/gov/endblocker_test.go b/x/gov/abci_test.go similarity index 91% rename from x/gov/endblocker_test.go rename to x/gov/abci_test.go index f197ae85a..42616b36c 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/abci_test.go @@ -9,11 +9,12 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + keep "github.com/cosmos/cosmos-sdk/x/gov/keeper" "github.com/cosmos/cosmos-sdk/x/staking" ) func TestTickExpiredDepositPeriod(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) + input := getMockApp(t, 10, GenesisState{}, nil, ProposalHandler) header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) @@ -62,7 +63,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { } func TestTickMultipleExpiredDepositPeriod(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) + input := getMockApp(t, 10, GenesisState{}, nil, ProposalHandler) header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) @@ -130,7 +131,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { } func TestTickPassedDepositPeriod(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) + input := getMockApp(t, 10, GenesisState{}, nil, ProposalHandler) header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) @@ -153,8 +154,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - var proposalID uint64 - input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID) + proposalID := GetProposalIDFromBytes(res.Data) inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) require.False(t, inactiveQueue.Valid()) @@ -178,7 +178,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { } func TestTickPassedVotingPeriod(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) + input := getMockApp(t, 10, GenesisState{}, nil, ProposalHandler) SortAddresses(input.addrs) header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} @@ -195,12 +195,11 @@ func TestTickPassedVotingPeriod(t *testing.T) { activeQueue.Close() proposalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(5))} - newProposalMsg := NewMsgSubmitProposal(testProposal(), proposalCoins, input.addrs[0]) + newProposalMsg := NewMsgSubmitProposal(keep.TestProposal, proposalCoins, input.addrs[0]) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - var proposalID uint64 - input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID) + proposalID := GetProposalIDFromBytes(res.Data) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) @@ -221,15 +220,11 @@ func TestTickPassedVotingPeriod(t *testing.T) { activeQueue = input.keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) require.True(t, activeQueue.Valid()) - var activeProposalID uint64 - - require.NoError(t, input.keeper.cdc.UnmarshalBinaryLengthPrefixed(activeQueue.Value(), &activeProposalID)) + activeProposalID := GetProposalIDFromBytes(activeQueue.Value()) proposal, ok := input.keeper.GetProposal(ctx, activeProposalID) require.True(t, ok) require.Equal(t, StatusVotingPeriod, proposal.Status) - depositsIterator := input.keeper.GetDepositsIterator(ctx, proposalID) - require.True(t, depositsIterator.Valid()) - depositsIterator.Close() + activeQueue.Close() EndBlocker(ctx, input.keeper) @@ -240,7 +235,7 @@ func TestTickPassedVotingPeriod(t *testing.T) { } func TestProposalPassedEndblocker(t *testing.T) { - input := getMockApp(t, 1, GenesisState{}, nil) + input := getMockApp(t, 1, GenesisState{}, nil, ProposalHandler) SortAddresses(input.addrs) handler := NewHandler(input.keeper) @@ -259,7 +254,7 @@ func TestProposalPassedEndblocker(t *testing.T) { require.NotNil(t, macc) initialModuleAccCoins := macc.GetCoins() - proposal, err := input.keeper.SubmitProposal(ctx, testProposal()) + proposal, err := input.keeper.SubmitProposal(ctx, keep.TestProposal) require.NoError(t, err) proposalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(10))} @@ -289,11 +284,9 @@ func TestProposalPassedEndblocker(t *testing.T) { } func TestEndBlockerProposalHandlerFailed(t *testing.T) { - input := getMockApp(t, 1, GenesisState{}, nil) - SortAddresses(input.addrs) - // hijack the router to one that will fail in a proposal's handler - input.keeper.router = NewRouter().AddRoute(RouterKey, badProposalHandler) + input := getMockApp(t, 1, GenesisState{}, nil, badProposalHandler) + SortAddresses(input.addrs) handler := NewHandler(input.keeper) stakingHandler := staking.NewHandler(input.sk) @@ -310,7 +303,7 @@ func TestEndBlockerProposalHandlerFailed(t *testing.T) { // Create a proposal where the handler will pass for the test proposal // because the value of contextKeyBadProposal is true. ctx = ctx.WithValue(contextKeyBadProposal, true) - proposal, err := input.keeper.SubmitProposal(ctx, testProposal()) + proposal, err := input.keeper.SubmitProposal(ctx, keep.TestProposal) require.NoError(t, err) proposalCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(10))) diff --git a/x/gov/alias.go b/x/gov/alias.go index 9e3ed0ed0..c8942f63f 100644 --- a/x/gov/alias.go +++ b/x/gov/alias.go @@ -1,10 +1,12 @@ // nolint // autogenerated code using github.com/rigelrozanski/multitool // aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/gov/keeper // ALIASGEN: github.com/cosmos/cosmos-sdk/x/gov/types package gov import ( + "github.com/cosmos/cosmos-sdk/x/gov/keeper" "github.com/cosmos/cosmos-sdk/x/gov/types" ) @@ -23,6 +25,7 @@ const ( CodeInvalidGenesis = types.CodeInvalidGenesis CodeInvalidProposalStatus = types.CodeInvalidProposalStatus CodeProposalHandlerNotExists = types.CodeProposalHandlerNotExists + DefaultPeriod = types.DefaultPeriod ModuleName = types.ModuleName StoreKey = types.StoreKey RouterKey = types.RouterKey @@ -59,6 +62,11 @@ const ( var ( // functions aliases + RegisterInvariants = keeper.RegisterInvariants + AllInvariants = keeper.AllInvariants + ModuleAccountInvariant = keeper.ModuleAccountInvariant + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier RegisterCodec = types.RegisterCodec RegisterProposalTypeCodec = types.RegisterProposalTypeCodec ValidateAbstract = types.ValidateAbstract @@ -66,13 +74,16 @@ var ( ErrUnknownProposal = types.ErrUnknownProposal ErrInactiveProposal = types.ErrInactiveProposal ErrAlreadyActiveProposal = types.ErrAlreadyActiveProposal - ErrAlreadyFinishedProposal = types.ErrAlreadyFinishedProposal - ErrAddressNotStaked = types.ErrAddressNotStaked ErrInvalidProposalContent = types.ErrInvalidProposalContent ErrInvalidProposalType = types.ErrInvalidProposalType ErrInvalidVote = types.ErrInvalidVote ErrInvalidGenesis = types.ErrInvalidGenesis ErrNoProposalHandlerExists = types.ErrNoProposalHandlerExists + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + GetProposalIDBytes = types.GetProposalIDBytes + GetProposalIDFromBytes = types.GetProposalIDFromBytes ProposalKey = types.ProposalKey ActiveProposalByTimeKey = types.ActiveProposalByTimeKey ActiveProposalQueueKey = types.ActiveProposalQueueKey @@ -96,11 +107,9 @@ var ( NewVotingParams = types.NewVotingParams NewParams = types.NewParams NewProposal = types.NewProposal + NewRouter = types.NewRouter ProposalStatusFromString = types.ProposalStatusFromString ValidProposalStatus = types.ValidProposalStatus - NewTallyResult = types.NewTallyResult - NewTallyResultFromMap = types.NewTallyResultFromMap - EmptyTallyResult = types.EmptyTallyResult NewTextProposal = types.NewTextProposal NewSoftwareUpgradeProposal = types.NewSoftwareUpgradeProposal RegisterProposalType = types.RegisterProposalType @@ -111,6 +120,10 @@ var ( NewQueryDepositParams = types.NewQueryDepositParams NewQueryVoteParams = types.NewQueryVoteParams NewQueryProposalsParams = types.NewQueryProposalsParams + NewValidatorGovInfo = types.NewValidatorGovInfo + NewTallyResult = types.NewTallyResult + NewTallyResultFromMap = types.NewTallyResultFromMap + EmptyTallyResult = types.EmptyTallyResult NewVote = types.NewVote VoteOptionFromString = types.VoteOptionFromString ValidVoteOption = types.ValidVoteOption @@ -129,10 +142,12 @@ var ( ) type ( + Keeper = keeper.Keeper Content = types.Content Handler = types.Handler Deposit = types.Deposit Deposits = types.Deposits + GenesisState = types.GenesisState MsgSubmitProposal = types.MsgSubmitProposal MsgDeposit = types.MsgDeposit MsgVote = types.MsgVote @@ -144,13 +159,14 @@ type ( Proposals = types.Proposals ProposalQueue = types.ProposalQueue ProposalStatus = types.ProposalStatus - TallyResult = types.TallyResult TextProposal = types.TextProposal SoftwareUpgradeProposal = types.SoftwareUpgradeProposal QueryProposalParams = types.QueryProposalParams QueryDepositParams = types.QueryDepositParams QueryVoteParams = types.QueryVoteParams QueryProposalsParams = types.QueryProposalsParams + ValidatorGovInfo = types.ValidatorGovInfo + TallyResult = types.TallyResult Vote = types.Vote Votes = types.Votes VoteOption = types.VoteOption diff --git a/x/gov/client/rest/query.go b/x/gov/client/rest/query.go new file mode 100644 index 000000000..f02942d5b --- /dev/null +++ b/x/gov/client/rest/query.go @@ -0,0 +1,499 @@ +package rest + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc(fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), queryParamsHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), queryProposerHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cliCtx)).Methods("GET") +} + + +func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + paramType := vars[RestParamsType] + + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + err := errors.New("proposalId required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + params := types.NewQueryProposalParams(proposalID) + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData("custom/gov/proposal", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryDepositsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + params := types.NewQueryProposalParams(proposalID) + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + var proposal types.Proposal + if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + // For inactive proposals we must query the txs directly to get the deposits + // as they're no longer in state. + propStatus := proposal.Status + if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { + res, err = gcutils.QueryDepositsByTxQuery(cliCtx, params) + } else { + res, _, err = cliCtx.QueryWithData("custom/gov/deposits", bz) + } + + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryProposerHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + res, err := gcutils.QueryProposerByTxQuery(cliCtx, proposalID) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryDepositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + bechDepositorAddr := vars[RestDepositor] + + if len(strProposalID) == 0 { + err := errors.New("proposalId required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + if len(bechDepositorAddr) == 0 { + err := errors.New("depositor address required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + params := types.NewQueryDepositParams(proposalID, depositorAddr) + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/gov/deposit", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + var deposit types.Deposit + if err := cliCtx.Codec.UnmarshalJSON(res, &deposit); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // For an empty deposit, either the proposal does not exist or is inactive in + // which case the deposit would be removed from state and should be queried + // for directly via a txs query. + if deposit.Empty() { + bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID)) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz) + if err != nil || len(res) == 0 { + err := fmt.Errorf("proposalID %d does not exist", proposalID) + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + res, err = gcutils.QueryDepositByTxQuery(cliCtx, params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryVoteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + bechVoterAddr := vars[RestVoter] + + if len(strProposalID) == 0 { + err := errors.New("proposalId required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + if len(bechVoterAddr) == 0 { + err := errors.New("voter address required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + params := types.NewQueryVoteParams(proposalID, voterAddr) + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/gov/vote", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + var vote types.Vote + if err := cliCtx.Codec.UnmarshalJSON(res, &vote); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // For an empty vote, either the proposal does not exist or is inactive in + // which case the vote would be removed from state and should be queried for + // directly via a txs query. + if vote.Empty() { + bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID)) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz) + if err != nil || len(res) == 0 { + err := fmt.Errorf("proposalID %d does not exist", proposalID) + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + res, err = gcutils.QueryVoteByTxQuery(cliCtx, params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// todo: Split this functionality into helper functions to remove the above +func queryVotesOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + err := errors.New("proposalId required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + params := types.NewQueryProposalParams(proposalID) + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + var proposal types.Proposal + if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + // For inactive proposals we must query the txs directly to get the votes + // as they're no longer in state. + propStatus := proposal.Status + if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { + res, err = gcutils.QueryVotesByTxQuery(cliCtx, params) + } else { + res, _, err = cliCtx.QueryWithData("custom/gov/votes", bz) + } + + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// todo: Split this functionality into helper functions to remove the above +func queryProposalsWithParameterFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + bechVoterAddr := r.URL.Query().Get(RestVoter) + bechDepositorAddr := r.URL.Query().Get(RestDepositor) + strProposalStatus := r.URL.Query().Get(RestProposalStatus) + strNumLimit := r.URL.Query().Get(RestNumLimit) + + params := types.QueryProposalsParams{} + + if len(bechVoterAddr) != 0 { + voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + params.Voter = voterAddr + } + + if len(bechDepositorAddr) != 0 { + depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + params.Depositor = depositorAddr + } + + if len(strProposalStatus) != 0 { + proposalStatus, err := types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(strProposalStatus)) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + params.ProposalStatus = proposalStatus + } + if len(strNumLimit) != 0 { + numLimit, ok := rest.ParseUint64OrReturnBadRequest(w, strNumLimit) + if !ok { + return + } + params.Limit = numLimit + } + + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData("custom/gov/proposals", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} + +// todo: Split this functionality into helper functions to remove the above +func queryTallyOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + err := errors.New("proposalId required but not specified") + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + params := types.NewQueryProposalParams(proposalID) + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData("custom/gov/tally", bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 9312a4e76..cfded3eee 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -1,8 +1,6 @@ package rest import ( - "errors" - "fmt" "net/http" "github.com/gorilla/mux" @@ -10,9 +8,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" - "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" - "github.com/cosmos/cosmos-sdk/x/gov/types" ) // REST Variable names @@ -35,31 +30,8 @@ type ProposalRESTHandler struct { // RegisterRoutes - Central function to define routes that get registered by the main application func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, phs []ProposalRESTHandler) { - propSubRtr := r.PathPrefix("/gov/proposals").Subrouter() - for _, ph := range phs { - propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST") - } - - r.HandleFunc("/gov/proposals", postProposalHandlerFn(cliCtx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cliCtx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cliCtx)).Methods("POST") - - r.HandleFunc( - fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), - queryParamsHandlerFn(cliCtx), - ).Methods("GET") - - r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cliCtx)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cliCtx)).Methods("GET") - r.HandleFunc( - fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), - queryProposerHandlerFn(cliCtx), - ).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cliCtx)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(cliCtx)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(cliCtx)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cliCtx)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cliCtx)).Methods("GET") + registerQueryRoutes(cliCtx, r) + registerTxRoutes(cliCtx, r, phs) } // PostProposalReq defines the properties of a proposal request's body. @@ -85,578 +57,3 @@ type VoteReq struct { Voter sdk.AccAddress `json:"voter" yaml:"voter"` // address of the voter Option string `json:"option" yaml:"option"` // option from OptionSet chosen by the voter } - -func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req PostProposalReq - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { - return - } - - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { - return - } - - proposalType := gcutils.NormalizeProposalType(req.ProposalType) - content := types.ContentFromProposalType(req.Title, req.Description, proposalType) - - msg := types.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer) - if err := msg.ValidateBasic(); err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) - } -} - -func depositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - var req DepositReq - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { - return - } - - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { - return - } - - // create the message - msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount) - if err := msg.ValidateBasic(); err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) - } -} - -func voteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - var req VoteReq - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { - return - } - - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { - return - } - - voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option)) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // create the message - msg := types.NewMsgVote(req.Voter, proposalID, voteOption) - if err := msg.ValidateBasic(); err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) - } -} - -func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - paramType := vars[RestParamsType] - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil) - if err != nil { - rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func queryProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryProposalParams(proposalID) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, height, err := cliCtx.QueryWithData("custom/gov/proposal", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func queryDepositsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryProposalParams(proposalID) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - var proposal types.Proposal - if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // For inactive proposals we must query the txs directly to get the deposits - // as they're no longer in state. - propStatus := proposal.Status - if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { - res, err = gcutils.QueryDepositsByTxQuery(cliCtx, params) - } else { - res, _, err = cliCtx.QueryWithData("custom/gov/deposits", bz) - } - - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func queryProposerHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - res, err := gcutils.QueryProposerByTxQuery(cliCtx, proposalID) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func queryDepositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - bechDepositorAddr := vars[RestDepositor] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - if len(bechDepositorAddr) == 0 { - err := errors.New("depositor address required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryDepositParams(proposalID, depositorAddr) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, _, err := cliCtx.QueryWithData("custom/gov/deposit", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - var deposit types.Deposit - if err := cliCtx.Codec.UnmarshalJSON(res, &deposit); err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // For an empty deposit, either the proposal does not exist or is inactive in - // which case the deposit would be removed from state and should be queried - // for directly via a txs query. - if deposit.Empty() { - bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID)) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz) - if err != nil || len(res) == 0 { - err := fmt.Errorf("proposalID %d does not exist", proposalID) - rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) - return - } - - res, err = gcutils.QueryDepositByTxQuery(cliCtx, params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - } - - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func queryVoteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - bechVoterAddr := vars[RestVoter] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - if len(bechVoterAddr) == 0 { - err := errors.New("voter address required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryVoteParams(proposalID, voterAddr) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, _, err := cliCtx.QueryWithData("custom/gov/vote", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - var vote types.Vote - if err := cliCtx.Codec.UnmarshalJSON(res, &vote); err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // For an empty vote, either the proposal does not exist or is inactive in - // which case the vote would be removed from state and should be queried for - // directly via a txs query. - if vote.Empty() { - bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID)) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz) - if err != nil || len(res) == 0 { - err := fmt.Errorf("proposalID %d does not exist", proposalID) - rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) - return - } - - res, err = gcutils.QueryVoteByTxQuery(cliCtx, params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - } - - rest.PostProcessResponse(w, cliCtx, res) - } -} - -// todo: Split this functionality into helper functions to remove the above -func queryVotesOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryProposalParams(proposalID) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - var proposal types.Proposal - if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // For inactive proposals we must query the txs directly to get the votes - // as they're no longer in state. - propStatus := proposal.Status - if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { - res, err = gcutils.QueryVotesByTxQuery(cliCtx, params) - } else { - res, _, err = cliCtx.QueryWithData("custom/gov/votes", bz) - } - - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cliCtx, res) - } -} - -// todo: Split this functionality into helper functions to remove the above -func queryProposalsWithParameterFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - bechVoterAddr := r.URL.Query().Get(RestVoter) - bechDepositorAddr := r.URL.Query().Get(RestDepositor) - strProposalStatus := r.URL.Query().Get(RestProposalStatus) - strNumLimit := r.URL.Query().Get(RestNumLimit) - - params := types.QueryProposalsParams{} - - if len(bechVoterAddr) != 0 { - voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - params.Voter = voterAddr - } - - if len(bechDepositorAddr) != 0 { - depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - params.Depositor = depositorAddr - } - - if len(strProposalStatus) != 0 { - proposalStatus, err := types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(strProposalStatus)) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - params.ProposalStatus = proposalStatus - } - if len(strNumLimit) != 0 { - numLimit, ok := rest.ParseUint64OrReturnBadRequest(w, strNumLimit) - if !ok { - return - } - params.Limit = numLimit - } - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, height, err := cliCtx.QueryWithData("custom/gov/proposals", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -// todo: Split this functionality into helper functions to remove the above -func queryTallyOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - strProposalID := vars[RestProposalID] - - if len(strProposalID) == 0 { - err := errors.New("proposalId required but not specified") - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) - if !ok { - return - } - - cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryProposalParams(proposalID) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, height, err := cliCtx.QueryWithData("custom/gov/tally", bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} diff --git a/x/gov/client/rest/tx.go b/x/gov/client/rest/tx.go new file mode 100644 index 000000000..c705bddf4 --- /dev/null +++ b/x/gov/client/rest/tx.go @@ -0,0 +1,129 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, phs []ProposalRESTHandler) { + propSubRtr := r.PathPrefix("/gov/proposals").Subrouter() + for _, ph := range phs { + propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST") + } + + r.HandleFunc("/gov/proposals", postProposalHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cliCtx)).Methods("POST") +} + +func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req PostProposalReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + proposalType := gcutils.NormalizeProposalType(req.ProposalType) + content := types.ContentFromProposalType(req.Title, req.Description, proposalType) + + msg := types.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} + +func depositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified") + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + var req DepositReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + // create the message + msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} + +func voteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + if len(strProposalID) == 0 { + rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified") + return + } + + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + var req VoteReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option)) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // create the message + msg := types.NewMsgVote(req.Voter, proposalID, voteOption) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + } +} diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 2ac972c4f..7283e3258 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -1,102 +1,19 @@ package gov import ( - "bytes" "fmt" - "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov/types" ) -const ( - // Default period for deposits & voting - DefaultPeriod time.Duration = 86400 * 2 * time.Second // 2 days -) - -// GenesisState - all staking state that must be provided at genesis -type GenesisState struct { - StartingProposalID uint64 `json:"starting_proposal_id" yaml:"starting_proposal_id"` - Deposits Deposits `json:"deposits" yaml:"deposits"` - Votes Votes `json:"votes" yaml:"votes"` - Proposals []Proposal `json:"proposals" yaml:"proposals"` - DepositParams DepositParams `json:"deposit_params" yaml:"deposit_params"` - VotingParams VotingParams `json:"voting_params" yaml:"voting_params"` - TallyParams TallyParams `json:"tally_params" yaml:"tally_params"` -} - -// NewGenesisState creates a new genesis state for the governance module -func NewGenesisState(startingProposalID uint64, dp DepositParams, vp VotingParams, tp TallyParams) GenesisState { - return GenesisState{ - StartingProposalID: startingProposalID, - DepositParams: dp, - VotingParams: vp, - TallyParams: tp, - } -} - -// get raw genesis raw message for testing -func DefaultGenesisState() GenesisState { - minDepositTokens := sdk.TokensFromConsensusPower(10) - return GenesisState{ - StartingProposalID: 1, - DepositParams: DepositParams{ - MinDeposit: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, minDepositTokens)}, - MaxDepositPeriod: DefaultPeriod, - }, - VotingParams: VotingParams{ - VotingPeriod: DefaultPeriod, - }, - TallyParams: TallyParams{ - Quorum: sdk.NewDecWithPrec(334, 3), - Threshold: sdk.NewDecWithPrec(5, 1), - Veto: sdk.NewDecWithPrec(334, 3), - }, - } -} - -// Checks whether 2 GenesisState structs are equivalent. -func (data GenesisState) Equal(data2 GenesisState) bool { - b1 := types.ModuleCdc.MustMarshalBinaryBare(data) - b2 := types.ModuleCdc.MustMarshalBinaryBare(data2) - return bytes.Equal(b1, b2) -} - -// Returns if a GenesisState is empty or has data in it -func (data GenesisState) IsEmpty() bool { - emptyGenState := GenesisState{} - return data.Equal(emptyGenState) -} - -// ValidateGenesis checks if parameters are within valid ranges -func ValidateGenesis(data GenesisState) error { - threshold := data.TallyParams.Threshold - if threshold.IsNegative() || threshold.GT(sdk.OneDec()) { - return fmt.Errorf("Governance vote threshold should be positive and less or equal to one, is %s", - threshold.String()) - } - - veto := data.TallyParams.Veto - if veto.IsNegative() || veto.GT(sdk.OneDec()) { - return fmt.Errorf("Governance vote veto threshold should be positive and less or equal to one, is %s", - veto.String()) - } - - if !data.DepositParams.MinDeposit.IsValid() { - return fmt.Errorf("Governance deposit amount must be a valid sdk.Coins amount, is %s", - data.DepositParams.MinDeposit.String()) - } - - return nil -} - // InitGenesis - store genesis parameters -func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper SupplyKeeper, data GenesisState) { +func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper types.SupplyKeeper, data GenesisState) { - k.setProposalID(ctx, data.StartingProposalID) - k.setDepositParams(ctx, data.DepositParams) - k.setVotingParams(ctx, data.VotingParams) - k.setTallyParams(ctx, data.TallyParams) + k.SetProposalID(ctx, data.StartingProposalID) + k.SetDepositParams(ctx, data.DepositParams) + k.SetVotingParams(ctx, data.VotingParams) + k.SetTallyParams(ctx, data.TallyParams) // check if the deposits pool account exists moduleAcc := k.GetGovernanceAccount(ctx) @@ -106,12 +23,12 @@ func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper SupplyKeeper, data Gene var totalDeposits sdk.Coins for _, deposit := range data.Deposits { - k.setDeposit(ctx, deposit.ProposalID, deposit.Depositor, deposit) + k.SetDeposit(ctx, deposit) totalDeposits = totalDeposits.Add(deposit.Amount) } for _, vote := range data.Votes { - k.setVote(ctx, vote.ProposalID, vote.Voter, vote) + k.SetVote(ctx, vote) } for _, proposal := range data.Proposals { diff --git a/x/gov/genesis_test.go b/x/gov/genesis_test.go index 692a77b6a..1beb0baec 100644 --- a/x/gov/genesis_test.go +++ b/x/gov/genesis_test.go @@ -3,71 +3,15 @@ package gov import ( "testing" + keep "github.com/cosmos/cosmos-sdk/x/gov/keeper" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) -func TestEqualProposalID(t *testing.T) { - state1 := GenesisState{} - state2 := GenesisState{} - require.Equal(t, state1, state2) - - // Proposals - state1.StartingProposalID = 1 - require.NotEqual(t, state1, state2) - require.False(t, state1.Equal(state2)) - - state2.StartingProposalID = 1 - require.Equal(t, state1, state2) - require.True(t, state1.Equal(state2)) -} - -func TestEqualProposals(t *testing.T) { - // Generate mock app and keepers - input := getMockApp(t, 2, GenesisState{}, nil) - SortAddresses(input.addrs) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - - // Submit two proposals - proposal := testProposal() - proposal1, err := input.keeper.SubmitProposal(ctx, proposal) - require.NoError(t, err) - proposal2, err := input.keeper.SubmitProposal(ctx, proposal) - require.NoError(t, err) - - // They are similar but their IDs should be different - require.NotEqual(t, proposal1, proposal2) - require.False(t, ProposalEqual(proposal1, proposal2)) - - // Now create two genesis blocks - state1 := GenesisState{Proposals: []Proposal{proposal1}} - state2 := GenesisState{Proposals: []Proposal{proposal2}} - require.NotEqual(t, state1, state2) - require.False(t, state1.Equal(state2)) - - // Now make proposals identical by setting both IDs to 55 - proposal1.ProposalID = 55 - proposal2.ProposalID = 55 - require.Equal(t, proposal1, proposal1) - require.True(t, ProposalEqual(proposal1, proposal2)) - - // Reassign proposals into state - state1.Proposals[0] = proposal1 - state2.Proposals[0] = proposal2 - - // State should be identical now.. - require.Equal(t, state1, state2) - require.True(t, state1.Equal(state2)) -} - func TestImportExportQueues(t *testing.T) { // Generate mock app and keepers - input := getMockApp(t, 2, GenesisState{}, nil) + input := getMockApp(t, 2, GenesisState{}, nil, ProposalHandler) SortAddresses(input.addrs) header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} @@ -76,7 +20,7 @@ func TestImportExportQueues(t *testing.T) { ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) // Create two proposals, put the second into the voting period - proposal := testProposal() + proposal := keep.TestProposal proposal1, err := input.keeper.SubmitProposal(ctx, proposal) require.NoError(t, err) proposalID1 := proposal1.ProposalID @@ -100,7 +44,7 @@ func TestImportExportQueues(t *testing.T) { // Export the state and import it into a new Mock App genState := ExportGenesis(ctx, input.keeper) - input2 := getMockApp(t, 2, genState, genAccs) + input2 := getMockApp(t, 2, genState, genAccs, ProposalHandler) header = abci.Header{Height: input.mApp.LastBlockHeight() + 1} input2.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) @@ -129,3 +73,45 @@ func TestImportExportQueues(t *testing.T) { require.True(t, ok) require.True(t, proposal2.Status == StatusRejected) } + +func TestEqualProposals(t *testing.T) { + // Generate mock app and keepers + input := getMockApp(t, 2, GenesisState{}, nil, ProposalHandler) + SortAddresses(input.addrs) + + header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} + input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + + ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) + + // Submit two proposals + proposal := keep.TestProposal + proposal1, err := input.keeper.SubmitProposal(ctx, proposal) + require.NoError(t, err) + proposal2, err := input.keeper.SubmitProposal(ctx, proposal) + require.NoError(t, err) + + // They are similar but their IDs should be different + require.NotEqual(t, proposal1, proposal2) + require.False(t, keep.ProposalEqual(proposal1, proposal2)) + + // Now create two genesis blocks + state1 := GenesisState{Proposals: []Proposal{proposal1}} + state2 := GenesisState{Proposals: []Proposal{proposal2}} + require.NotEqual(t, state1, state2) + require.False(t, state1.Equal(state2)) + + // Now make proposals identical by setting both IDs to 55 + proposal1.ProposalID = 55 + proposal2.ProposalID = 55 + require.Equal(t, proposal1, proposal1) + require.True(t, keep.ProposalEqual(proposal1, proposal2)) + + // Reassign proposals into state + state1.Proposals[0] = proposal1 + state2.Proposals[0] = proposal2 + + // State should be identical now.. + require.Equal(t, state1, state2) + require.True(t, state1.Equal(state2)) +} diff --git a/x/gov/handler.go b/x/gov/handler.go index 11011baff..cd116264c 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov/types" ) -// Handle all "gov" type messages. +// NewHandler creates an sdk.Handler for all the gov type messages func NewHandler(keeper Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { ctx = ctx.WithEventManager(sdk.NewEventManager()) @@ -58,7 +58,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos } return sdk.Result{ - Data: keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal.ProposalID), + Data: GetProposalIDBytes(proposal.ProposalID), Events: ctx.EventManager().Events(), } } diff --git a/x/gov/deposit.go b/x/gov/keeper/deposit.go similarity index 59% rename from x/gov/deposit.go rename to x/gov/keeper/deposit.go index 03b1ab7d3..426d43d4f 100644 --- a/x/gov/deposit.go +++ b/x/gov/keeper/deposit.go @@ -1,4 +1,4 @@ -package gov +package keeper import ( "fmt" @@ -8,7 +8,7 @@ import ( ) // GetDeposit gets the deposit of a specific depositor on a specific proposal -func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) (deposit Deposit, found bool) { +func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) (deposit types.Deposit, found bool) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(types.DepositKey(proposalID, depositorAddr)) if bz == nil { @@ -19,10 +19,76 @@ func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAdd return deposit, true } -func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, deposit Deposit) { +// SetDeposit sets a Deposit to the gov store +func (keeper Keeper) SetDeposit(ctx sdk.Context, deposit types.Deposit) { store := ctx.KVStore(keeper.storeKey) bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit) - store.Set(types.DepositKey(proposalID, depositorAddr), bz) + store.Set(types.DepositKey(deposit.ProposalID, deposit.Depositor), bz) +} + +// GetAllDeposits returns all the deposits from the store +func (keeper Keeper) GetAllDeposits(ctx sdk.Context) (deposits types.Deposits) { + keeper.IterateAllDeposits(ctx, func(deposit types.Deposit) bool { + deposits = append(deposits, deposit) + return false + }) + return +} + +// GetDeposits returns all the deposits from a proposal +func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) (deposits types.Deposits) { + keeper.IterateDeposits(ctx, proposalID, func(deposit types.Deposit) bool { + deposits = append(deposits, deposit) + return false + }) + return +} + +// DeleteDeposits deletes all the deposits on a specific proposal without refunding them +func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + + keeper.IterateDeposits(ctx, proposalID, func(deposit types.Deposit) bool { + err := keeper.supplyKeeper.BurnCoins(ctx, types.ModuleName, deposit.Amount) + if err != nil { + panic(err) + } + + store.Delete(types.DepositKey(proposalID, deposit.Depositor)) + return false + }) +} + +// IterateAllDeposits iterates over the all the stored deposits and performs a callback function +func (keeper Keeper) IterateAllDeposits(ctx sdk.Context, cb func(deposit types.Deposit) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.DepositsKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var deposit types.Deposit + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit) + + if cb(deposit) { + break + } + } +} + +// IterateDeposits iterates over the all the proposals deposits and performs a callback function +func (keeper Keeper) IterateDeposits(ctx sdk.Context, proposalID uint64, cb func(deposit types.Deposit) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.DepositsKey(proposalID)) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var deposit types.Deposit + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit) + + if cb(deposit) { + break + } + } } // AddDeposit adds or updates a deposit of a specific depositor on a specific proposal @@ -31,12 +97,12 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd // Checks to see if proposal exists proposal, ok := keeper.GetProposal(ctx, proposalID) if !ok { - return ErrUnknownProposal(keeper.codespace, proposalID), false + return types.ErrUnknownProposal(keeper.codespace, proposalID), false } // Check if proposal is still depositable - if (proposal.Status != StatusDepositPeriod) && (proposal.Status != StatusVotingPeriod) { - return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false + if (proposal.Status != types.StatusDepositPeriod) && (proposal.Status != types.StatusVotingPeriod) { + return types.ErrInactiveProposal(keeper.codespace, proposalID), false } // update the governance module's account coins pool @@ -51,7 +117,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd // Check if deposit has provided sufficient total funds to transition the proposal into the voting period activatedVotingPeriod := false - if proposal.Status == StatusDepositPeriod && proposal.TotalDeposit.IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { + if proposal.Status == types.StatusDepositPeriod && proposal.TotalDeposit.IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { keeper.activateVotingPeriod(ctx, proposal) activatedVotingPeriod = true } @@ -61,7 +127,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd if found { deposit.Amount = deposit.Amount.Add(depositAmount) } else { - deposit = NewDeposit(proposalID, depositorAddr, depositAmount) + deposit = types.NewDeposit(proposalID, depositorAddr, depositAmount) } ctx.EventManager().EmitEvent( @@ -72,34 +138,10 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd ), ) - keeper.setDeposit(ctx, proposalID, depositorAddr, deposit) + keeper.SetDeposit(ctx, deposit) return nil, activatedVotingPeriod } -// GetAllDeposits returns all the deposits from the store -func (keeper Keeper) GetAllDeposits(ctx sdk.Context) (deposits Deposits) { - keeper.IterateAllDeposits(ctx, func(deposit Deposit) bool { - deposits = append(deposits, deposit) - return false - }) - return -} - -// GetDeposits returns all the deposits from a proposal -func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) (deposits Deposits) { - keeper.IterateDeposits(ctx, proposalID, func(deposit Deposit) bool { - deposits = append(deposits, deposit) - return false - }) - return -} - -// GetDepositsIterator gets all the deposits on a specific proposal as an sdk.Iterator -func (keeper Keeper) GetDepositsIterator(ctx sdk.Context, proposalID uint64) sdk.Iterator { - store := ctx.KVStore(keeper.storeKey) - return sdk.KVStorePrefixIterator(store, types.DepositsKey(proposalID)) -} - // RefundDeposits refunds and deletes all the deposits on a specific proposal func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) @@ -110,22 +152,7 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { panic(err) } - store.Delete(DepositKey(proposalID, deposit.Depositor)) - return false - }) -} - -// DeleteDeposits deletes all the deposits on a specific proposal without refunding them -func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - - keeper.IterateDeposits(ctx, proposalID, func(deposit types.Deposit) bool { - err := keeper.supplyKeeper.BurnCoins(ctx, types.ModuleName, deposit.Amount) - if err != nil { - panic(err) - } - - store.Delete(DepositKey(proposalID, deposit.Depositor)) + store.Delete(types.DepositKey(proposalID, deposit.Depositor)) return false }) } diff --git a/x/gov/keeper/deposit_test.go b/x/gov/keeper/deposit_test.go new file mode 100644 index 000000000..c6c229893 --- /dev/null +++ b/x/gov/keeper/deposit_test.go @@ -0,0 +1,98 @@ +package keeper + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestDeposits(t *testing.T) { + ctx, ak, keeper, _, _ := createTestInput(t, false, 100) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + + fourStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(4))) + fiveStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(5))) + + addr0Initial := ak.GetAccount(ctx, TestAddrs[0]).GetCoins() + addr1Initial := ak.GetAccount(ctx, TestAddrs[1]).GetCoins() + + require.True(t, proposal.TotalDeposit.IsEqual(sdk.NewCoins())) + + // Check no deposits at beginning + deposit, found := keeper.GetDeposit(ctx, proposalID, TestAddrs[1]) + require.False(t, found) + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.True(t, proposal.VotingStartTime.Equal(time.Time{})) + + // Check first deposit + err, votingStarted := keeper.AddDeposit(ctx, proposalID, TestAddrs[0], fourStake) + require.NoError(t, err) + require.False(t, votingStarted) + deposit, found = keeper.GetDeposit(ctx, proposalID, TestAddrs[0]) + require.True(t, found) + require.Equal(t, fourStake, deposit.Amount) + require.Equal(t, TestAddrs[0], deposit.Depositor) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.Equal(t, fourStake, proposal.TotalDeposit) + require.Equal(t, addr0Initial.Sub(fourStake), ak.GetAccount(ctx, TestAddrs[0]).GetCoins()) + + // Check a second deposit from same address + err, votingStarted = keeper.AddDeposit(ctx, proposalID, TestAddrs[0], fiveStake) + require.NoError(t, err) + require.False(t, votingStarted) + deposit, found = keeper.GetDeposit(ctx, proposalID, TestAddrs[0]) + require.True(t, found) + require.Equal(t, fourStake.Add(fiveStake), deposit.Amount) + require.Equal(t, TestAddrs[0], deposit.Depositor) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.Equal(t, fourStake.Add(fiveStake), proposal.TotalDeposit) + require.Equal(t, addr0Initial.Sub(fourStake).Sub(fiveStake), ak.GetAccount(ctx, TestAddrs[0]).GetCoins()) + + // Check third deposit from a new address + err, votingStarted = keeper.AddDeposit(ctx, proposalID, TestAddrs[1], fourStake) + require.NoError(t, err) + require.True(t, votingStarted) + deposit, found = keeper.GetDeposit(ctx, proposalID, TestAddrs[1]) + require.True(t, found) + require.Equal(t, TestAddrs[1], deposit.Depositor) + require.Equal(t, fourStake, deposit.Amount) + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.Equal(t, fourStake.Add(fiveStake).Add(fourStake), proposal.TotalDeposit) + require.Equal(t, addr1Initial.Sub(fourStake), ak.GetAccount(ctx, TestAddrs[1]).GetCoins()) + + // Check that proposal moved to voting period + proposal, ok = keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) + + // Test deposit iterator + // NOTE order of deposits is determined by the addresses + deposits := keeper.GetAllDeposits(ctx) + require.Len(t, deposits, 2) + require.Equal(t, deposits, keeper.GetDeposits(ctx, proposalID)) + require.Equal(t, TestAddrs[0], deposits[0].Depositor) + require.Equal(t, fourStake.Add(fiveStake), deposits[0].Amount) + require.Equal(t, TestAddrs[1], deposits[1].Depositor) + require.Equal(t, fourStake, deposits[1].Amount) + + // Test Refund Deposits + deposit, found = keeper.GetDeposit(ctx, proposalID, TestAddrs[1]) + require.True(t, found) + require.Equal(t, fourStake, deposit.Amount) + keeper.RefundDeposits(ctx, proposalID) + deposit, found = keeper.GetDeposit(ctx, proposalID, TestAddrs[1]) + require.False(t, found) + require.Equal(t, addr0Initial, ak.GetAccount(ctx, TestAddrs[0]).GetCoins()) + require.Equal(t, addr1Initial, ak.GetAccount(ctx, TestAddrs[1]).GetCoins()) +} diff --git a/x/gov/invariants.go b/x/gov/keeper/invariants.go similarity index 97% rename from x/gov/invariants.go rename to x/gov/keeper/invariants.go index 9c98262f5..2350ea553 100644 --- a/x/gov/invariants.go +++ b/x/gov/keeper/invariants.go @@ -1,4 +1,6 @@ -package gov +package keeper + +// DONTCOVER import ( "fmt" diff --git a/x/gov/keeper.go b/x/gov/keeper/keeper.go similarity index 52% rename from x/gov/keeper.go rename to x/gov/keeper/keeper.go index 826ef83cd..b322d3e6b 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper/keeper.go @@ -1,4 +1,4 @@ -package gov +package keeper import ( "fmt" @@ -7,25 +7,21 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/supply/exported" "github.com/tendermint/tendermint/libs/log" ) -// Governance Keeper +// Keeper defines the governance module Keeper type Keeper struct { - // The reference to the Param Keeper to get and set Global Params - paramsKeeper params.Keeper - // The reference to the Paramstore to get and set gov specific params - paramSpace params.Subspace + paramSpace types.ParamSubspace // The SupplyKeeper to reduce the supply of the network - supplyKeeper SupplyKeeper + supplyKeeper types.SupplyKeeper // The reference to the DelegationSet and ValidatorSet to get information about validators and delegators - sk StakingKeeper + sk types.StakingKeeper // The (unexposed) keys used to access the stores from the Context. storeKey sdk.StoreKey @@ -37,7 +33,7 @@ type Keeper struct { codespace sdk.CodespaceType // Proposal router - router Router + router types.Router } // NewKeeper returns a governance keeper. It handles: @@ -45,9 +41,11 @@ type Keeper struct { // - depositing funds into proposals, and activating upon sufficient funds being deposited // - users voting on proposals, with weight proportional to stake in the system // - and tallying the result of the vote. +// +// CONTRACT: the parameter Subspace must have the param key table already initialized func NewKeeper( - cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, paramSpace params.Subspace, - supplyKeeper SupplyKeeper, sk StakingKeeper, codespace sdk.CodespaceType, rtr Router, + cdc *codec.Codec, key sdk.StoreKey, paramSpace types.ParamSubspace, + supplyKeeper types.SupplyKeeper, sk types.StakingKeeper, codespace sdk.CodespaceType, rtr types.Router, ) Keeper { // ensure governance module account is set @@ -62,8 +60,7 @@ func NewKeeper( return Keeper{ storeKey: key, - paramsKeeper: paramsKeeper, - paramSpace: paramSpace.WithKeyTable(ParamKeyTable()), + paramSpace: paramSpace, supplyKeeper: supplyKeeper, sk: sk, cdc: cdc, @@ -77,52 +74,22 @@ func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } +// Router returns the gov Keeper's Router +func (keeper Keeper) Router() types.Router { + return keeper.router +} + // GetGovernanceAccount returns the governance ModuleAccount func (keeper Keeper) GetGovernanceAccount(ctx sdk.Context) exported.ModuleAccountI { return keeper.supplyKeeper.GetModuleAccount(ctx, types.ModuleName) } -// Params - -// Returns the current DepositParams from the global param store -func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams { - var depositParams DepositParams - keeper.paramSpace.Get(ctx, ParamStoreKeyDepositParams, &depositParams) - return depositParams -} - -// Returns the current VotingParams from the global param store -func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams { - var votingParams VotingParams - keeper.paramSpace.Get(ctx, ParamStoreKeyVotingParams, &votingParams) - return votingParams -} - -// Returns the current TallyParam from the global param store -func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams { - var tallyParams TallyParams - keeper.paramSpace.Get(ctx, ParamStoreKeyTallyParams, &tallyParams) - return tallyParams -} - -func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) { - keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams) -} - -func (keeper Keeper) setVotingParams(ctx sdk.Context, votingParams VotingParams) { - keeper.paramSpace.Set(ctx, ParamStoreKeyVotingParams, &votingParams) -} - -func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { - keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams) -} - // ProposalQueues // InsertActiveProposalQueue inserts a ProposalID into the active proposal queue at endTime func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, proposalID uint64, endTime time.Time) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + bz := types.GetProposalIDBytes(proposalID) store.Set(types.ActiveProposalQueueKey(proposalID, endTime), bz) } @@ -135,7 +102,7 @@ func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, proposalID u // InsertInactiveProposalQueue Inserts a ProposalID into the inactive proposal queue at endTime func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, proposalID uint64, endTime time.Time) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + bz := types.GetProposalIDBytes(proposalID) store.Set(types.InactiveProposalQueueKey(proposalID, endTime), bz) } @@ -147,22 +114,6 @@ func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, proposalID // Iterators -// IterateProposals iterates over the all the proposals and performs a callback function -func (keeper Keeper) IterateProposals(ctx sdk.Context, cb func(proposal types.Proposal) (stop bool)) { - store := ctx.KVStore(keeper.storeKey) - iterator := sdk.KVStorePrefixIterator(store, types.ProposalsKeyPrefix) - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var proposal types.Proposal - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &proposal) - - if cb(proposal) { - break - } - } -} - // IterateActiveProposalsQueue iterates over the proposals in the active proposal queue // and performs a callback function func (keeper Keeper) IterateActiveProposalsQueue(ctx sdk.Context, endTime time.Time, cb func(proposal types.Proposal) (stop bool)) { @@ -201,76 +152,14 @@ func (keeper Keeper) IterateInactiveProposalsQueue(ctx sdk.Context, endTime time } } -// IterateAllDeposits iterates over the all the stored deposits and performs a callback function -func (keeper Keeper) IterateAllDeposits(ctx sdk.Context, cb func(deposit types.Deposit) (stop bool)) { - store := ctx.KVStore(keeper.storeKey) - iterator := sdk.KVStorePrefixIterator(store, types.DepositsKeyPrefix) - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var deposit types.Deposit - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit) - - if cb(deposit) { - break - } - } -} - -// IterateDeposits iterates over the all the proposals deposits and performs a callback function -func (keeper Keeper) IterateDeposits(ctx sdk.Context, proposalID uint64, cb func(deposit types.Deposit) (stop bool)) { - iterator := keeper.GetDepositsIterator(ctx, proposalID) - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var deposit types.Deposit - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit) - - if cb(deposit) { - break - } - } -} - -// IterateAllVotes iterates over the all the stored votes and performs a callback function -func (keeper Keeper) IterateAllVotes(ctx sdk.Context, cb func(vote types.Vote) (stop bool)) { - store := ctx.KVStore(keeper.storeKey) - iterator := sdk.KVStorePrefixIterator(store, types.VotesKeyPrefix) - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var vote types.Vote - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) - - if cb(vote) { - break - } - } -} - -// IterateVotes iterates over the all the proposals votes and performs a callback function -func (keeper Keeper) IterateVotes(ctx sdk.Context, proposalID uint64, cb func(vote types.Vote) (stop bool)) { - iterator := keeper.GetVotesIterator(ctx, proposalID) - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var vote types.Vote - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) - - if cb(vote) { - break - } - } -} - // ActiveProposalQueueIterator returns an sdk.Iterator for all the proposals in the Active Queue that expire by endTime func (keeper Keeper) ActiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - return store.Iterator(ActiveProposalQueuePrefix, sdk.PrefixEndBytes(types.ActiveProposalByTimeKey(endTime))) + return store.Iterator(types.ActiveProposalQueuePrefix, sdk.PrefixEndBytes(types.ActiveProposalByTimeKey(endTime))) } // InactiveProposalQueueIterator returns an sdk.Iterator for all the proposals in the Inactive Queue that expire by endTime func (keeper Keeper) InactiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - return store.Iterator(InactiveProposalQueuePrefix, sdk.PrefixEndBytes(types.InactiveProposalByTimeKey(endTime))) + return store.Iterator(types.InactiveProposalQueuePrefix, sdk.PrefixEndBytes(types.InactiveProposalByTimeKey(endTime))) } diff --git a/x/gov/keeper/keeper_test.go b/x/gov/keeper/keeper_test.go new file mode 100644 index 000000000..9832c6df3 --- /dev/null +++ b/x/gov/keeper/keeper_test.go @@ -0,0 +1,50 @@ +package keeper + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/stretchr/testify/require" +) + +func TestIncrementProposalNumber(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 100) + + tp := TestProposal + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + keeper.SubmitProposal(ctx, tp) + proposal6, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + + require.Equal(t, uint64(6), proposal6.ProposalID) +} + +func TestProposalQueues(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 100) + + // create test proposals + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.DepositEndTime) + require.True(t, inactiveIterator.Valid()) + + proposalID := types.GetProposalIDFromBytes(inactiveIterator.Value()) + require.Equal(t, proposalID, proposal.ProposalID) + inactiveIterator.Close() + + keeper.activateVotingPeriod(ctx, proposal) + + proposal, ok := keeper.GetProposal(ctx, proposal.ProposalID) + require.True(t, ok) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime) + require.True(t, activeIterator.Valid()) + keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.ProposalID) + activeIterator.Close() +} diff --git a/x/gov/keeper/params.go b/x/gov/keeper/params.go new file mode 100644 index 000000000..0c6fc4338 --- /dev/null +++ b/x/gov/keeper/params.go @@ -0,0 +1,42 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// GetDepositParams returns the current DepositParams from the global param store +func (keeper Keeper) GetDepositParams(ctx sdk.Context) types.DepositParams { + var depositParams types.DepositParams + keeper.paramSpace.Get(ctx, types.ParamStoreKeyDepositParams, &depositParams) + return depositParams +} + +// GetVotingParams returns the current VotingParams from the global param store +func (keeper Keeper) GetVotingParams(ctx sdk.Context) types.VotingParams { + var votingParams types.VotingParams + keeper.paramSpace.Get(ctx, types.ParamStoreKeyVotingParams, &votingParams) + return votingParams +} + +// GetTallyParams returns the current TallyParam from the global param store +func (keeper Keeper) GetTallyParams(ctx sdk.Context) types.TallyParams { + var tallyParams types.TallyParams + keeper.paramSpace.Get(ctx, types.ParamStoreKeyTallyParams, &tallyParams) + return tallyParams +} + +// SetDepositParams sets DepositParams to the global param store +func (keeper Keeper) SetDepositParams(ctx sdk.Context, depositParams types.DepositParams) { + keeper.paramSpace.Set(ctx, types.ParamStoreKeyDepositParams, &depositParams) +} + +// SetVotingParams sets VotingParams to the global param store +func (keeper Keeper) SetVotingParams(ctx sdk.Context, votingParams types.VotingParams) { + keeper.paramSpace.Set(ctx, types.ParamStoreKeyVotingParams, &votingParams) +} + +// SetTallyParams sets TallyParams to the global param store +func (keeper Keeper) SetTallyParams(ctx sdk.Context, tallyParams types.TallyParams) { + keeper.paramSpace.Set(ctx, types.ParamStoreKeyTallyParams, &tallyParams) +} diff --git a/x/gov/proposal.go b/x/gov/keeper/proposal.go similarity index 65% rename from x/gov/proposal.go rename to x/gov/keeper/proposal.go index ef9b7e89e..4ad9ea8b9 100644 --- a/x/gov/proposal.go +++ b/x/gov/keeper/proposal.go @@ -1,4 +1,4 @@ -package gov +package keeper import ( "fmt" @@ -8,9 +8,9 @@ import ( ) // SubmitProposal create new proposal given a content -func (keeper Keeper) SubmitProposal(ctx sdk.Context, content Content) (Proposal, sdk.Error) { +func (keeper Keeper) SubmitProposal(ctx sdk.Context, content types.Content) (types.Proposal, sdk.Error) { if !keeper.router.HasRoute(content.ProposalRoute()) { - return Proposal{}, ErrNoProposalHandlerExists(keeper.codespace, content) + return types.Proposal{}, types.ErrNoProposalHandlerExists(keeper.codespace, content) } // Execute the proposal content in a cache-wrapped context to validate the @@ -19,22 +19,22 @@ func (keeper Keeper) SubmitProposal(ctx sdk.Context, content Content) (Proposal, cacheCtx, _ := ctx.CacheContext() handler := keeper.router.GetRoute(content.ProposalRoute()) if err := handler(cacheCtx, content); err != nil { - return Proposal{}, ErrInvalidProposalContent(keeper.codespace, err.Result().Log) + return types.Proposal{}, types.ErrInvalidProposalContent(keeper.codespace, err.Result().Log) } proposalID, err := keeper.GetProposalID(ctx) if err != nil { - return Proposal{}, err + return types.Proposal{}, err } submitTime := ctx.BlockHeader().Time depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod - proposal := NewProposal(content, proposalID, submitTime, submitTime.Add(depositPeriod)) + proposal := types.NewProposal(content, proposalID, submitTime, submitTime.Add(depositPeriod)) keeper.SetProposal(ctx, proposal) keeper.InsertInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime) - keeper.setProposalID(ctx, proposalID+1) + keeper.SetProposalID(ctx, proposalID+1) ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -46,10 +46,10 @@ func (keeper Keeper) SubmitProposal(ctx sdk.Context, content Content) (Proposal, return proposal, nil } -// GetProposal get Proposal from store by ProposalID -func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal Proposal, ok bool) { +// GetProposal get proposal from store by ProposalID +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal types.Proposal, ok bool) { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(ProposalKey(proposalID)) + bz := store.Get(types.ProposalKey(proposalID)) if bz == nil { return } @@ -58,10 +58,10 @@ func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal P } // SetProposal set a proposal to store -func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { +func (keeper Keeper) SetProposal(ctx sdk.Context, proposal types.Proposal) { store := ctx.KVStore(keeper.storeKey) bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) - store.Set(ProposalKey(proposal.ProposalID), bz) + store.Set(types.ProposalKey(proposal.ProposalID), bz) } // DeleteProposal deletes a proposal from store @@ -73,11 +73,27 @@ func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { } keeper.RemoveFromInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime) keeper.RemoveFromActiveProposalQueue(ctx, proposalID, proposal.VotingEndTime) - store.Delete(ProposalKey(proposalID)) + store.Delete(types.ProposalKey(proposalID)) +} + +// IterateProposals iterates over the all the proposals and performs a callback function +func (keeper Keeper) IterateProposals(ctx sdk.Context, cb func(proposal types.Proposal) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.ProposalsKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var proposal types.Proposal + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &proposal) + + if cb(proposal) { + break + } + } } // GetProposals returns all the proposals from store -func (keeper Keeper) GetProposals(ctx sdk.Context) (proposals Proposals) { +func (keeper Keeper) GetProposals(ctx sdk.Context) (proposals types.Proposals) { keeper.IterateProposals(ctx, func(proposal types.Proposal) bool { proposals = append(proposals, proposal) return false @@ -90,14 +106,14 @@ func (keeper Keeper) GetProposals(ctx sdk.Context) (proposals Proposals) { // depositorAddr will filter proposals by whether or not that address has deposited to them // status will filter proposals by status // numLatest will fetch a specified number of the most recent proposals, or 0 for all proposals -func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal { +func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status types.ProposalStatus, numLatest uint64) []types.Proposal { maxProposalID, err := keeper.GetProposalID(ctx) if err != nil { - return []Proposal{} + return []types.Proposal{} } - matchingProposals := []Proposal{} + matchingProposals := []types.Proposal{} if numLatest == 0 { numLatest = maxProposalID @@ -123,7 +139,7 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr continue } - if ValidProposalStatus(status) && proposal.Status != status { + if types.ValidProposalStatus(status) && proposal.Status != status { continue } @@ -135,26 +151,25 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr // GetProposalID gets the highest proposal ID func (keeper Keeper) GetProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(ProposalIDKey) + bz := store.Get(types.ProposalIDKey) if bz == nil { - return 0, ErrInvalidGenesis(keeper.codespace, "initial proposal ID hasn't been set") + return 0, types.ErrInvalidGenesis(keeper.codespace, "initial proposal ID hasn't been set") } - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + proposalID = types.GetProposalIDFromBytes(bz) return proposalID, nil } -// Set the proposal ID -func (keeper Keeper) setProposalID(ctx sdk.Context, proposalID uint64) { +// SetProposalID sets the new proposal ID to the store +func (keeper Keeper) SetProposalID(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) - store.Set(ProposalIDKey, bz) + store.Set(types.ProposalIDKey, types.GetProposalIDBytes(proposalID)) } -func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { +func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal types.Proposal) { proposal.VotingStartTime = ctx.BlockHeader().Time votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod proposal.VotingEndTime = proposal.VotingStartTime.Add(votingPeriod) - proposal.Status = StatusVotingPeriod + proposal.Status = types.StatusVotingPeriod keeper.SetProposal(ctx, proposal) keeper.RemoveFromInactiveProposalQueue(ctx, proposal.ProposalID, proposal.DepositEndTime) diff --git a/x/gov/keeper/proposal_test.go b/x/gov/keeper/proposal_test.go new file mode 100644 index 000000000..f36b9d8ab --- /dev/null +++ b/x/gov/keeper/proposal_test.go @@ -0,0 +1,122 @@ +package keeper + +import ( + "strings" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/stretchr/testify/require" +) + +func TestGetSetProposal(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 100) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + keeper.SetProposal(ctx, proposal) + + gotProposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + require.True(t, ProposalEqual(proposal, gotProposal)) +} + +func TestActivateVotingPeriod(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 100) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + + require.True(t, proposal.VotingStartTime.Equal(time.Time{})) + + keeper.activateVotingPeriod(ctx, proposal) + + require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) + + proposal, ok := keeper.GetProposal(ctx, proposal.ProposalID) + require.True(t, ok) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime) + require.True(t, activeIterator.Valid()) + + proposalID := types.GetProposalIDFromBytes(activeIterator.Value()) + require.Equal(t, proposalID, proposal.ProposalID) + activeIterator.Close() +} + +type validProposal struct{} + +func (validProposal) GetTitle() string { return "title" } +func (validProposal) GetDescription() string { return "description" } +func (validProposal) ProposalRoute() string { return types.RouterKey } +func (validProposal) ProposalType() string { return types.ProposalTypeText } +func (validProposal) String() string { return "" } +func (validProposal) ValidateBasic() sdk.Error { return nil } + +type invalidProposalTitle1 struct{ validProposal } + +func (invalidProposalTitle1) GetTitle() string { return "" } + +type invalidProposalTitle2 struct{ validProposal } + +func (invalidProposalTitle2) GetTitle() string { return strings.Repeat("1234567890", 100) } + +type invalidProposalDesc1 struct{ validProposal } + +func (invalidProposalDesc1) GetDescription() string { return "" } + +type invalidProposalDesc2 struct{ validProposal } + +func (invalidProposalDesc2) GetDescription() string { return strings.Repeat("1234567890", 1000) } + +type invalidProposalRoute struct{ validProposal } + +func (invalidProposalRoute) ProposalRoute() string { return "nonexistingroute" } + +type invalidProposalValidation struct{ validProposal } + +func (invalidProposalValidation) ValidateBasic() sdk.Error { + return sdk.NewError(sdk.CodespaceUndefined, sdk.CodeInternal, "") +} + +func registerTestCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(validProposal{}, "test/validproposal", nil) + cdc.RegisterConcrete(invalidProposalTitle1{}, "test/invalidproposalt1", nil) + cdc.RegisterConcrete(invalidProposalTitle2{}, "test/invalidproposalt2", nil) + cdc.RegisterConcrete(invalidProposalDesc1{}, "test/invalidproposald1", nil) + cdc.RegisterConcrete(invalidProposalDesc2{}, "test/invalidproposald2", nil) + cdc.RegisterConcrete(invalidProposalRoute{}, "test/invalidproposalr", nil) + cdc.RegisterConcrete(invalidProposalValidation{}, "test/invalidproposalv", nil) +} + +func TestSubmitProposal(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 100) + + registerTestCodec(keeper.cdc) + + testCases := []struct { + content types.Content + expectedErr sdk.Error + }{ + {validProposal{}, nil}, + // Keeper does not check the validity of title and description, no error + {invalidProposalTitle1{}, nil}, + {invalidProposalTitle2{}, nil}, + {invalidProposalDesc1{}, nil}, + {invalidProposalDesc2{}, nil}, + // error only when invalid route + {invalidProposalRoute{}, types.ErrNoProposalHandlerExists(types.DefaultCodespace, invalidProposalRoute{})}, + // Keeper does not call ValidateBasic, msg.ValidateBasic does + {invalidProposalValidation{}, nil}, + } + + for _, tc := range testCases { + _, err := keeper.SubmitProposal(ctx, tc.content) + require.Equal(t, tc.expectedErr, err, "unexpected type of error: %s", err) + } +} diff --git a/x/gov/querier.go b/x/gov/keeper/querier.go similarity index 85% rename from x/gov/querier.go rename to x/gov/keeper/querier.go index 03e37c84a..4ffcaa04a 100644 --- a/x/gov/querier.go +++ b/x/gov/keeper/querier.go @@ -1,4 +1,4 @@ -package gov +package keeper import ( "fmt" @@ -10,24 +10,25 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov/types" ) +// NewQuerier creates a new gov Querier instance func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { switch path[0] { case types.QueryParams: return queryParams(ctx, path[1:], req, keeper) - case QueryProposals: + case types.QueryProposals: return queryProposals(ctx, path[1:], req, keeper) - case QueryProposal: + case types.QueryProposal: return queryProposal(ctx, path[1:], req, keeper) - case QueryDeposits: + case types.QueryDeposits: return queryDeposits(ctx, path[1:], req, keeper) - case QueryDeposit: + case types.QueryDeposit: return queryDeposit(ctx, path[1:], req, keeper) - case QueryVotes: + case types.QueryVotes: return queryVotes(ctx, path[1:], req, keeper) - case QueryVote: + case types.QueryVote: return queryVote(ctx, path[1:], req, keeper) - case QueryTally: + case types.QueryTally: return queryTally(ctx, path[1:], req, keeper) default: return nil, sdk.ErrUnknownRequest("unknown gov query endpoint") @@ -37,19 +38,19 @@ func NewQuerier(keeper Keeper) sdk.Querier { func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { switch path[0] { - case ParamDeposit: + case types.ParamDeposit: bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetDepositParams(ctx)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } return bz, nil - case ParamVoting: + case types.ParamVoting: bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetVotingParams(ctx)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } return bz, nil - case ParamTallying: + case types.ParamTallying: bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetTallyParams(ctx)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) @@ -62,7 +63,7 @@ func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, keeper K // nolint: unparam func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryProposalParams + var params types.QueryProposalParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) @@ -70,7 +71,7 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper proposal, ok := keeper.GetProposal(ctx, params.ProposalID) if !ok { - return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + return nil, types.ErrUnknownProposal(types.DefaultCodespace, params.ProposalID) } bz, err := codec.MarshalJSONIndent(keeper.cdc, proposal) @@ -82,7 +83,7 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // nolint: unparam func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryDepositParams + var params types.QueryDepositParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) @@ -98,7 +99,7 @@ func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // nolint: unparam func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryVoteParams + var params types.QueryVoteParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) @@ -114,7 +115,7 @@ func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Kee // nolint: unparam func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryProposalParams + var params types.QueryProposalParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) @@ -131,7 +132,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper // nolint: unparam func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryProposalParams + var params types.QueryProposalParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) @@ -141,18 +142,18 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke proposal, ok := keeper.GetProposal(ctx, proposalID) if !ok { - return nil, ErrUnknownProposal(DefaultCodespace, proposalID) + return nil, types.ErrUnknownProposal(types.DefaultCodespace, proposalID) } - var tallyResult TallyResult + var tallyResult types.TallyResult - if proposal.Status == StatusDepositPeriod { - tallyResult = EmptyTallyResult() - } else if proposal.Status == StatusPassed || proposal.Status == StatusRejected { + if proposal.Status == types.StatusDepositPeriod { + tallyResult = types.EmptyTallyResult() + } else if proposal.Status == types.StatusPassed || proposal.Status == types.StatusRejected { tallyResult = proposal.FinalTallyResult } else { // proposal is in voting period - _, _, tallyResult = tally(ctx, keeper, proposal) + _, _, tallyResult = keeper.Tally(ctx, proposal) } bz, err := codec.MarshalJSONIndent(keeper.cdc, tallyResult) @@ -164,7 +165,7 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke // nolint: unparam func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryProposalParams + var params types.QueryProposalParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { @@ -182,7 +183,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke // nolint: unparam func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params QueryProposalsParams + var params types.QueryProposalsParams err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) diff --git a/x/gov/keeper/querier_test.go b/x/gov/keeper/querier_test.go new file mode 100644 index 000000000..818b95e3a --- /dev/null +++ b/x/gov/keeper/querier_test.go @@ -0,0 +1,315 @@ +package keeper + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +const custom = "custom" + +func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (types.DepositParams, types.VotingParams, types.TallyParams) { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryParams, types.ParamDeposit}, "/"), + Data: []byte{}, + } + + bz, err := querier(ctx, []string{types.QueryParams, types.ParamDeposit}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var depositParams types.DepositParams + require.NoError(t, cdc.UnmarshalJSON(bz, &depositParams)) + + query = abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryParams, types.ParamVoting}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{types.QueryParams, types.ParamVoting}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var votingParams types.VotingParams + require.NoError(t, cdc.UnmarshalJSON(bz, &votingParams)) + + query = abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryParams, types.ParamTallying}, "/"), + Data: []byte{}, + } + + bz, err = querier(ctx, []string{types.QueryParams, types.ParamTallying}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var tallyParams types.TallyParams + require.NoError(t, cdc.UnmarshalJSON(bz, &tallyParams)) + + return depositParams, votingParams, tallyParams +} + +func getQueriedProposal(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) types.Proposal { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryProposal}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryProposalParams(proposalID)), + } + + bz, err := querier(ctx, []string{types.QueryProposal}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var proposal types.Proposal + require.NoError(t, cdc.UnmarshalJSON(bz, proposal)) + + return proposal +} + +func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, depositor, voter sdk.AccAddress, status types.ProposalStatus, limit uint64) []types.Proposal { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryProposals}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryProposalsParams(status, limit, voter, depositor)), + } + + bz, err := querier(ctx, []string{types.QueryProposals}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var proposals types.Proposals + require.NoError(t, cdc.UnmarshalJSON(bz, &proposals)) + + return proposals +} + +func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, depositor sdk.AccAddress) types.Deposit { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryDeposit}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryDepositParams(proposalID, depositor)), + } + + bz, err := querier(ctx, []string{types.QueryDeposit}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var deposit types.Deposit + require.NoError(t, cdc.UnmarshalJSON(bz, &deposit)) + + return deposit +} + +func getQueriedDeposits(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []types.Deposit { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryDeposits}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryProposalParams(proposalID)), + } + + bz, err := querier(ctx, []string{types.QueryDeposits}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var deposits []types.Deposit + require.NoError(t, cdc.UnmarshalJSON(bz, &deposits)) + + return deposits +} + +func getQueriedVote(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, voter sdk.AccAddress) types.Vote { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryVote}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryVoteParams(proposalID, voter)), + } + + bz, err := querier(ctx, []string{types.QueryVote}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var vote types.Vote + require.NoError(t, cdc.UnmarshalJSON(bz, &vote)) + + return vote +} + +func getQueriedVotes(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []types.Vote { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryVote}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryProposalParams(proposalID)), + } + + bz, err := querier(ctx, []string{types.QueryVotes}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var votes []types.Vote + require.NoError(t, cdc.UnmarshalJSON(bz, &votes)) + + return votes +} + +func getQueriedTally(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) types.TallyResult { + query := abci.RequestQuery{ + Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryTally}, "/"), + Data: cdc.MustMarshalJSON(types.NewQueryProposalParams(proposalID)), + } + + bz, err := querier(ctx, []string{types.QueryTally}, query) + require.NoError(t, err) + require.NotNil(t, bz) + + var tally types.TallyResult + require.NoError(t, cdc.UnmarshalJSON(bz, &tally)) + + return tally +} + +func TestQueries(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 1000) + querier := NewQuerier(keeper) + + oneCoins := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)) + consCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(10))) + + tp := TestProposal + + depositParams, _, _ := getQueriedParams(t, ctx, keeper.cdc, querier) + + // TestAddrs[0] proposes (and deposits) proposals #1 and #2 + proposal1, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + deposit1 := types.NewDeposit(proposal1.ProposalID, TestAddrs[0], oneCoins) + err, _ = keeper.AddDeposit(ctx, deposit1.ProposalID, deposit1.Depositor, deposit1.Amount) + require.NoError(t, err) + + proposal1.TotalDeposit = proposal1.TotalDeposit.Add(deposit1.Amount) + + proposal2, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + deposit2 := types.NewDeposit(proposal2.ProposalID, TestAddrs[0], consCoins) + err, _ = keeper.AddDeposit(ctx, deposit2.ProposalID, deposit2.Depositor, deposit2.Amount) + require.NoError(t, err) + + proposal2.TotalDeposit = proposal2.TotalDeposit.Add(deposit2.Amount) + + // TestAddrs[1] proposes (and deposits) on proposal #3 + proposal3, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + deposit3 := types.NewDeposit(proposal3.ProposalID, TestAddrs[1], oneCoins) + err, _ = keeper.AddDeposit(ctx, deposit3.ProposalID, deposit3.Depositor, deposit3.Amount) + require.NoError(t, err) + + proposal3.TotalDeposit = proposal3.TotalDeposit.Add(deposit3.Amount) + + // TestAddrs[1] deposits on proposals #2 & #3 + deposit4 := types.NewDeposit(proposal2.ProposalID, TestAddrs[1], depositParams.MinDeposit) + err, _ = keeper.AddDeposit(ctx, deposit4.ProposalID, deposit4.Depositor, deposit4.Amount) + require.NoError(t, err) + + proposal2.TotalDeposit = proposal2.TotalDeposit.Add(deposit4.Amount) + proposal2.Status = types.StatusVotingPeriod + proposal2.VotingEndTime = proposal2.VotingEndTime.Add(types.DefaultPeriod) + + deposit5 := types.NewDeposit(proposal3.ProposalID, TestAddrs[1], depositParams.MinDeposit) + err, _ = keeper.AddDeposit(ctx, deposit5.ProposalID, deposit5.Depositor, deposit5.Amount) + require.NoError(t, err) + + proposal3.TotalDeposit = proposal3.TotalDeposit.Add(deposit5.Amount) + proposal3.Status = types.StatusVotingPeriod + proposal3.VotingEndTime = proposal3.VotingEndTime.Add(types.DefaultPeriod) + // total deposit of TestAddrs[1] on proposal #3 is worth the proposal deposit + individual deposit + deposit5.Amount = deposit5.Amount.Add(deposit3.Amount) + + // check deposits on proposal1 match individual deposits + deposits := getQueriedDeposits(t, ctx, keeper.cdc, querier, proposal1.ProposalID) + require.Len(t, deposits, 1) + require.Equal(t, deposit1, deposits[0]) + + deposit := getQueriedDeposit(t, ctx, keeper.cdc, querier, proposal1.ProposalID, TestAddrs[0]) + require.Equal(t, deposit1, deposit) + + // check deposits on proposal2 match individual deposits + deposits = getQueriedDeposits(t, ctx, keeper.cdc, querier, proposal2.ProposalID) + require.Len(t, deposits, 2) + // NOTE order of deposits is determined by the addresses + require.Equal(t, deposit2, deposits[0]) + require.Equal(t, deposit4, deposits[1]) + + deposit = getQueriedDeposit(t, ctx, keeper.cdc, querier, proposal2.ProposalID, TestAddrs[0]) + require.Equal(t, deposit2, deposits[0]) + deposit = getQueriedDeposit(t, ctx, keeper.cdc, querier, proposal2.ProposalID, TestAddrs[1]) + require.Equal(t, deposit4, deposits[1]) + + // check deposits on proposal3 match individual deposits + deposits = getQueriedDeposits(t, ctx, keeper.cdc, querier, proposal3.ProposalID) + require.Len(t, deposits, 1) + require.Equal(t, deposit5, deposits[0]) + + deposit = getQueriedDeposit(t, ctx, keeper.cdc, querier, proposal3.ProposalID, TestAddrs[1]) + require.Equal(t, deposit5, deposit) + + // Only proposal #1 should be in types.Deposit Period + proposals := getQueriedProposals(t, ctx, keeper.cdc, querier, nil, nil, types.StatusDepositPeriod, 0) + require.Len(t, proposals, 1) + require.Equal(t, proposal1, proposals[0]) + + // Only proposals #2 and #3 should be in Voting Period + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, nil, nil, types.StatusVotingPeriod, 0) + require.Len(t, proposals, 2) + require.Equal(t, proposal2, proposals[0]) + require.Equal(t, proposal3, proposals[1]) + + // Addrs[0] votes on proposals #2 & #3 + vote1 := types.NewVote(proposal2.ProposalID, TestAddrs[0], types.OptionYes) + vote2 := types.NewVote(proposal3.ProposalID, TestAddrs[0], types.OptionYes) + keeper.SetVote(ctx, vote1) + keeper.SetVote(ctx, vote2) + + // Addrs[1] votes on proposal #3 + vote3 := types.NewVote(proposal3.ProposalID, TestAddrs[1], types.OptionYes) + keeper.SetVote(ctx, vote3) + + // Test query voted by TestAddrs[0] + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, nil, TestAddrs[0], types.StatusNil, 0) + require.Equal(t, proposal2, proposals[0]) + require.Equal(t, proposal3, proposals[1]) + + // Test query votes on types.Proposal 2 + votes := getQueriedVotes(t, ctx, keeper.cdc, querier, proposal2.ProposalID) + require.Len(t, votes, 1) + require.Equal(t, vote1, votes[0]) + + vote := getQueriedVote(t, ctx, keeper.cdc, querier, proposal2.ProposalID, TestAddrs[0]) + require.Equal(t, vote1, vote) + + // Test query votes on types.Proposal 3 + votes = getQueriedVotes(t, ctx, keeper.cdc, querier, proposal3.ProposalID) + require.Len(t, votes, 2) + require.Equal(t, vote2, votes[0]) + require.Equal(t, vote3, votes[1]) + + // Test query all proposals + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, nil, nil, types.StatusNil, 0) + require.Equal(t, proposal1, proposals[0]) + require.Equal(t, proposal2, proposals[1]) + require.Equal(t, proposal3, proposals[2]) + + // Test query voted by TestAddrs[1] + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, nil, TestAddrs[1], types.StatusNil, 0) + require.Equal(t, proposal3.ProposalID, proposals[0].ProposalID) + + // Test query deposited by TestAddrs[0] + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, TestAddrs[0], nil, types.StatusNil, 0) + require.Equal(t, proposal1.ProposalID, proposals[0].ProposalID) + + // Test query deposited by addr2 + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, TestAddrs[1], nil, types.StatusNil, 0) + require.Equal(t, proposal2.ProposalID, proposals[0].ProposalID) + require.Equal(t, proposal3.ProposalID, proposals[1].ProposalID) + + // Test query voted AND deposited by addr1 + proposals = getQueriedProposals(t, ctx, keeper.cdc, querier, TestAddrs[0], TestAddrs[0], types.StatusNil, 0) + require.Equal(t, proposal2.ProposalID, proposals[0].ProposalID) +} diff --git a/x/gov/tally.go b/x/gov/keeper/tally.go similarity index 65% rename from x/gov/tally.go rename to x/gov/keeper/tally.go index 58160b8ab..1bd908203 100644 --- a/x/gov/tally.go +++ b/x/gov/keeper/tally.go @@ -1,4 +1,4 @@ -package gov +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -6,46 +6,28 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/exported" ) -// validatorGovInfo used for tallying -type validatorGovInfo struct { - Address sdk.ValAddress // address of the validator operator - BondedTokens sdk.Int // Power of a Validator - DelegatorShares sdk.Dec // Total outstanding delegator shares - DelegatorDeductions sdk.Dec // Delegator deductions from validator's delegators voting independently - Vote VoteOption // Vote of the validator -} - -func newValidatorGovInfo(address sdk.ValAddress, bondedTokens sdk.Int, delegatorShares, - delegatorDeductions sdk.Dec, vote VoteOption) validatorGovInfo { - - return validatorGovInfo{ - Address: address, - BondedTokens: bondedTokens, - DelegatorShares: delegatorShares, - DelegatorDeductions: delegatorDeductions, - Vote: vote, - } -} - // TODO: Break into several smaller functions for clarity -func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burnDeposits bool, tallyResults TallyResult) { - results := make(map[VoteOption]sdk.Dec) - results[OptionYes] = sdk.ZeroDec() - results[OptionAbstain] = sdk.ZeroDec() - results[OptionNo] = sdk.ZeroDec() - results[OptionNoWithVeto] = sdk.ZeroDec() + +// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the +// voters +func (keeper Keeper) Tally(ctx sdk.Context, proposal types.Proposal) (passes bool, burnDeposits bool, tallyResults types.TallyResult) { + results := make(map[types.VoteOption]sdk.Dec) + results[types.OptionYes] = sdk.ZeroDec() + results[types.OptionAbstain] = sdk.ZeroDec() + results[types.OptionNo] = sdk.ZeroDec() + results[types.OptionNoWithVeto] = sdk.ZeroDec() totalVotingPower := sdk.ZeroDec() - currValidators := make(map[string]validatorGovInfo) + currValidators := make(map[string]types.ValidatorGovInfo) // fetch all the bonded validators, insert them into currValidators keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator exported.ValidatorI) (stop bool) { - currValidators[validator.GetOperator().String()] = newValidatorGovInfo( + currValidators[validator.GetOperator().String()] = types.NewValidatorGovInfo( validator.GetOperator(), validator.GetBondedTokens(), validator.GetDelegatorShares(), sdk.ZeroDec(), - OptionEmpty, + types.OptionEmpty, ) return false @@ -84,7 +66,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn // iterate over the validators again to tally their voting power for _, val := range currValidators { - if val.Vote == OptionEmpty { + if val.Vote == types.OptionEmpty { continue } @@ -97,7 +79,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn } tallyParams := keeper.GetTallyParams(ctx) - tallyResults = NewTallyResultFromMap(results) + tallyResults = types.NewTallyResultFromMap(results) // TODO: Upgrade the spec to cover all of these cases & remove pseudocode. // If there is no staked coins, the proposal fails @@ -112,17 +94,17 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn } // If no one votes (everyone abstains), proposal fails - if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroDec()) { + if totalVotingPower.Sub(results[types.OptionAbstain]).Equal(sdk.ZeroDec()) { return false, false, tallyResults } // If more than 1/3 of voters veto, proposal fails - if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyParams.Veto) { + if results[types.OptionNoWithVeto].Quo(totalVotingPower).GT(tallyParams.Veto) { return false, true, tallyResults } // If more than 1/2 of non-abstaining voters vote Yes, proposal passes - if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyParams.Threshold) { + if results[types.OptionYes].Quo(totalVotingPower.Sub(results[types.OptionAbstain])).GT(tallyParams.Threshold) { return true, false, tallyResults } diff --git a/x/gov/keeper/tally_test.go b/x/gov/keeper/tally_test.go new file mode 100644 index 000000000..dcf370289 --- /dev/null +++ b/x/gov/keeper/tally_test.go @@ -0,0 +1,398 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +func TestTallyNoOneVotes(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 5, 5}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.True(t, burnDeposits) + require.True(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyNoQuorum(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{2, 5, 0}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + err = keeper.AddVote(ctx, proposalID, TestAddrs[0], types.OptionYes) + require.Nil(t, err) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, _ := keeper.Tally(ctx, proposal) + require.False(t, passes) + require.True(t, burnDeposits) +} + +func TestTallyOnlyValidatorsAllYes(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 5, 5}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionYes)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.True(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyOnlyValidators51No(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 6, 0}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, _ := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.False(t, burnDeposits) +} + +func TestTallyOnlyValidators51Yes(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 6, 0}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionNo)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionYes)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.True(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyOnlyValidatorsVetoed(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{6, 6, 7}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionNoWithVeto)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.True(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) + +} + +func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{6, 6, 7}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionAbstain)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionYes)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.True(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{6, 6, 7}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionAbstain)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyOnlyValidatorsNonVoter(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 6, 7}) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyDelgatorOverride(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 6, 7}) + + delTokens := sdk.TokensFromConsensusPower(30) + val1, found := sk.GetValidator(ctx, valOpAddr1) + require.True(t, found) + + _, err := sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val1, true) + require.NoError(t, err) + + _ = staking.EndBlocker(ctx, sk) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, TestAddrs[0], types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyDelgatorInherit(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 6, 7}) + + delTokens := sdk.TokensFromConsensusPower(30) + val3, found := sk.GetValidator(ctx, valOpAddr3) + require.True(t, found) + + _, err := sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val3, true) + require.NoError(t, err) + + _ = staking.EndBlocker(ctx, sk) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionNo)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionYes)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.True(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyDelgatorMultipleOverride(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{5, 6, 7}) + + delTokens := sdk.TokensFromConsensusPower(10) + val1, found := sk.GetValidator(ctx, valOpAddr1) + require.True(t, found) + val2, found := sk.GetValidator(ctx, valOpAddr2) + require.True(t, found) + + _, err := sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val1, true) + require.NoError(t, err) + _, err = sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val2, true) + require.NoError(t, err) + + _ = staking.EndBlocker(ctx, sk) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, TestAddrs[0], types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyDelgatorMultipleInherit(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{25, 6, 7}) + + delTokens := sdk.TokensFromConsensusPower(10) + val2, found := sk.GetValidator(ctx, valOpAddr2) + require.True(t, found) + val3, found := sk.GetValidator(ctx, valOpAddr3) + require.True(t, found) + + _, err := sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val2, true) + require.NoError(t, err) + _, err = sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val3, true) + require.NoError(t, err) + + _ = staking.EndBlocker(ctx, sk) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.False(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} + +func TestTallyJailedValidator(t *testing.T) { + ctx, _, keeper, sk, _ := createTestInput(t, false, 100) + createValidators(ctx, sk, []int64{25, 6, 7}) + + delTokens := sdk.TokensFromConsensusPower(10) + val2, found := sk.GetValidator(ctx, valOpAddr2) + require.True(t, found) + val3, found := sk.GetValidator(ctx, valOpAddr3) + require.True(t, found) + + _, err := sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val2, true) + require.NoError(t, err) + _, err = sk.Delegate(ctx, TestAddrs[0], delTokens, sdk.Unbonded, val3, true) + require.NoError(t, err) + + _ = staking.EndBlocker(ctx, sk) + + sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address())) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr1, types.OptionYes)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr2, types.OptionNo)) + require.NoError(t, keeper.AddVote(ctx, proposalID, valAccAddr3, types.OptionNo)) + + proposal, ok := keeper.GetProposal(ctx, proposalID) + require.True(t, ok) + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + + require.True(t, passes) + require.False(t, burnDeposits) + require.False(t, tallyResults.Equals(types.EmptyTallyResult())) +} diff --git a/x/gov/keeper/test_common.go b/x/gov/keeper/test_common.go new file mode 100644 index 000000000..e822af3f6 --- /dev/null +++ b/x/gov/keeper/test_common.go @@ -0,0 +1,206 @@ +// nolint +package keeper // noalias + +// DONTCOVER + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tm-db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" +) + +// dummy addresses used for testing +var ( + delPk1 = newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51") + delPk2 = newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50") + delPk3 = newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52") + delAddr1 = sdk.AccAddress(delPk1.Address()) + delAddr2 = sdk.AccAddress(delPk2.Address()) + delAddr3 = sdk.AccAddress(delPk3.Address()) + + valOpPk1 = newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53") + valOpPk2 = newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54") + valOpPk3 = newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55") + valOpAddr1 = sdk.ValAddress(valOpPk1.Address()) + valOpAddr2 = sdk.ValAddress(valOpPk2.Address()) + valOpAddr3 = sdk.ValAddress(valOpPk3.Address()) + valAccAddr1 = sdk.AccAddress(valOpPk1.Address()) + valAccAddr2 = sdk.AccAddress(valOpPk2.Address()) + valAccAddr3 = sdk.AccAddress(valOpPk3.Address()) + + TestAddrs = []sdk.AccAddress{ + delAddr1, delAddr2, delAddr3, + valAccAddr1, valAccAddr2, valAccAddr3, + } + + emptyDelAddr sdk.AccAddress + emptyValAddr sdk.ValAddress + emptyPubkey crypto.PubKey +) + +// TODO: remove dependency with staking +var ( + TestProposal = types.NewTextProposal("Test", "description") + TestDescription = staking.NewDescription("T", "E", "S", "T") + TestCommissionRates = staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) +) + +// TODO move to common testing framework +func newPubKey(pk string) (res crypto.PubKey) { + pkBytes, err := hex.DecodeString(pk) + if err != nil { + panic(err) + } + var pkEd ed25519.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + return pkEd +} + +func makeTestCodec() *codec.Codec { + var cdc = codec.New() + auth.RegisterCodec(cdc) + types.RegisterCodec(cdc) + supply.RegisterCodec(cdc) + staking.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + + return cdc +} + +func createTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context, auth.AccountKeeper, Keeper, staking.Keeper, types.SupplyKeeper) { + + initTokens := sdk.TokensFromConsensusPower(initPower) + + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyGov := sdk.NewKVStoreKey(types.StoreKey) + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyGov, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + require.Nil(t, ms.LoadLatestVersion()) + + ctx := sdk.NewContext(ms, abci.Header{ChainID: "gov-chain"}, isCheckTx, log.NewNopLogger()) + ctx = ctx.WithConsensusParams( + &abci.ConsensusParams{ + Validator: &abci.ValidatorParams{ + PubKeyTypes: []string{tmtypes.ABCIPubKeyTypeEd25519}, + }, + }, + ) + cdc := makeTestCodec() + + maccPerms := map[string][]string{ + auth.FeeCollectorName: nil, + types.ModuleName: nil, + staking.NotBondedPoolName: {supply.Burner, supply.Staking}, + staking.BondedPoolName: {supply.Burner, supply.Staking}, + } + + // create module accounts + feeCollectorAcc := supply.NewEmptyModuleAccount(auth.FeeCollectorName) + govAcc := supply.NewEmptyModuleAccount(types.ModuleName, supply.Burner) + notBondedPool := supply.NewEmptyModuleAccount(staking.NotBondedPoolName, supply.Burner, supply.Staking) + bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking) + + blacklistedAddrs := make(map[string]bool) + blacklistedAddrs[feeCollectorAcc.GetAddress().String()] = true + blacklistedAddrs[govAcc.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true + + pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) + bankKeeper := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) + supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bankKeeper, maccPerms) + + sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + sk.SetParams(ctx, staking.DefaultParams()) + + rtr := types.NewRouter(). + AddRoute(types.RouterKey, types.ProposalHandler) + + keeper := NewKeeper(cdc, keyGov, pk.Subspace(types.DefaultParamspace).WithKeyTable(types.ParamKeyTable()), + supplyKeeper, sk, types.DefaultCodespace, rtr) + + keeper.SetProposalID(ctx, types.DefaultStartingProposalID) + keeper.SetDepositParams(ctx, types.DefaultDepositParams()) + keeper.SetVotingParams(ctx, types.DefaultVotingParams()) + keeper.SetTallyParams(ctx, types.DefaultTallyParams()) + + initCoins := sdk.NewCoins(sdk.NewCoin(sk.BondDenom(ctx), initTokens)) + totalSupply := sdk.NewCoins(sdk.NewCoin(sk.BondDenom(ctx), initTokens.MulRaw(int64(len(TestAddrs))))) + supplyKeeper.SetSupply(ctx, supply.NewSupply(totalSupply)) + + for _, addr := range TestAddrs { + _, err := bankKeeper.AddCoins(ctx, addr, initCoins) + require.Nil(t, err) + } + + keeper.supplyKeeper.SetModuleAccount(ctx, feeCollectorAcc) + keeper.supplyKeeper.SetModuleAccount(ctx, govAcc) + keeper.supplyKeeper.SetModuleAccount(ctx, bondPool) + keeper.supplyKeeper.SetModuleAccount(ctx, notBondedPool) + + return ctx, accountKeeper, keeper, sk, supplyKeeper +} + +// ProposalEqual checks if two proposals are equal (note: slow, for tests only) +func ProposalEqual(proposalA types.Proposal, proposalB types.Proposal) bool { + return bytes.Equal(types.ModuleCdc.MustMarshalBinaryBare(proposalA), + types.ModuleCdc.MustMarshalBinaryBare(proposalB)) +} + +func createValidators(ctx sdk.Context, sk staking.Keeper, powers []int64) { + val1 := staking.NewValidator(valOpAddr1, valOpPk1, staking.Description{}) + val2 := staking.NewValidator(valOpAddr2, valOpPk2, staking.Description{}) + val3 := staking.NewValidator(valOpAddr3, valOpPk3, staking.Description{}) + + sk.SetValidator(ctx, val1) + sk.SetValidator(ctx, val2) + sk.SetValidator(ctx, val3) + sk.SetValidatorByConsAddr(ctx, val1) + sk.SetValidatorByConsAddr(ctx, val2) + sk.SetValidatorByConsAddr(ctx, val3) + sk.SetNewValidatorByPowerIndex(ctx, val1) + sk.SetNewValidatorByPowerIndex(ctx, val2) + sk.SetNewValidatorByPowerIndex(ctx, val3) + + _, _ = sk.Delegate(ctx, valAccAddr1, sdk.TokensFromConsensusPower(powers[0]), sdk.Unbonded, val1, true) + _, _ = sk.Delegate(ctx, valAccAddr2, sdk.TokensFromConsensusPower(powers[1]), sdk.Unbonded, val2, true) + _, _ = sk.Delegate(ctx, valAccAddr3, sdk.TokensFromConsensusPower(powers[2]), sdk.Unbonded, val3, true) + + _ = staking.EndBlocker(ctx, sk) +} diff --git a/x/gov/keeper/vote.go b/x/gov/keeper/vote.go new file mode 100644 index 000000000..49ea2f704 --- /dev/null +++ b/x/gov/keeper/vote.go @@ -0,0 +1,111 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// AddVote adds a vote on a specific proposal +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option types.VoteOption) sdk.Error { + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + return types.ErrUnknownProposal(keeper.codespace, proposalID) + } + if proposal.Status != types.StatusVotingPeriod { + return types.ErrInactiveProposal(keeper.codespace, proposalID) + } + + if !types.ValidVoteOption(option) { + return types.ErrInvalidVote(keeper.codespace, option) + } + + vote := types.NewVote(proposalID, voterAddr, option) + keeper.SetVote(ctx, vote) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeProposalVote, + sdk.NewAttribute(types.AttributeKeyOption, option.String()), + sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposalID)), + ), + ) + + return nil +} + +// GetAllVotes returns all the votes from the store +func (keeper Keeper) GetAllVotes(ctx sdk.Context) (votes types.Votes) { + keeper.IterateAllVotes(ctx, func(vote types.Vote) bool { + votes = append(votes, vote) + return false + }) + return +} + +// GetVotes returns all the votes from a proposal +func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) (votes types.Votes) { + keeper.IterateVotes(ctx, proposalID, func(vote types.Vote) bool { + votes = append(votes, vote) + return false + }) + return +} + +// GetVote gets the vote from an address on a specific proposal +func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (vote types.Vote, found bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(types.VoteKey(proposalID, voterAddr)) + if bz == nil { + return vote, false + } + + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) + return vote, true +} + +// SetVote sets a Vote to the gov store +func (keeper Keeper) SetVote(ctx sdk.Context, vote types.Vote) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) + store.Set(types.VoteKey(vote.ProposalID, vote.Voter), bz) +} + +// IterateAllVotes iterates over the all the stored votes and performs a callback function +func (keeper Keeper) IterateAllVotes(ctx sdk.Context, cb func(vote types.Vote) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.VotesKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var vote types.Vote + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) + + if cb(vote) { + break + } + } +} + +// IterateVotes iterates over the all the proposals votes and performs a callback function +func (keeper Keeper) IterateVotes(ctx sdk.Context, proposalID uint64, cb func(vote types.Vote) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.VotesKey(proposalID)) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var vote types.Vote + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) + + if cb(vote) { + break + } + } +} + +// deleteVote deletes a vote from a given proposalID and voter from the store +func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(types.VoteKey(proposalID, voterAddr)) +} diff --git a/x/gov/keeper/vote_test.go b/x/gov/keeper/vote_test.go new file mode 100644 index 000000000..c15978e42 --- /dev/null +++ b/x/gov/keeper/vote_test.go @@ -0,0 +1,65 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +func TestVotes(t *testing.T) { + ctx, _, keeper, _, _ := createTestInput(t, false, 100) + + tp := TestProposal + proposal, err := keeper.SubmitProposal(ctx, tp) + require.NoError(t, err) + proposalID := proposal.ProposalID + + var invalidOption types.VoteOption + invalidOption = 0x10 + + require.Error(t, keeper.AddVote(ctx, proposalID, TestAddrs[0], types.OptionYes), "proposal not on voting period") + require.Error(t, keeper.AddVote(ctx, 10, TestAddrs[0], types.OptionYes), "invalid proposal ID") + + proposal.Status = types.StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + require.Error(t, keeper.AddVote(ctx, proposalID, TestAddrs[0], invalidOption), "invalid option") + + // Test first vote + require.NoError(t, keeper.AddVote(ctx, proposalID, TestAddrs[0], types.OptionAbstain)) + vote, found := keeper.GetVote(ctx, proposalID, TestAddrs[0]) + require.True(t, found) + require.Equal(t, TestAddrs[0], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, types.OptionAbstain, vote.Option) + + // Test change of vote + require.NoError(t, keeper.AddVote(ctx, proposalID, TestAddrs[0], types.OptionYes)) + vote, found = keeper.GetVote(ctx, proposalID, TestAddrs[0]) + require.True(t, found) + require.Equal(t, TestAddrs[0], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, types.OptionYes, vote.Option) + + // Test second vote + require.NoError(t, keeper.AddVote(ctx, proposalID, TestAddrs[1], types.OptionNoWithVeto)) + vote, found = keeper.GetVote(ctx, proposalID, TestAddrs[1]) + require.True(t, found) + require.Equal(t, TestAddrs[1], vote.Voter) + require.Equal(t, proposalID, vote.ProposalID) + require.Equal(t, types.OptionNoWithVeto, vote.Option) + + // Test vote iterator + // NOTE order of deposits is determined by the addresses + votes := keeper.GetAllVotes(ctx) + require.Len(t, votes, 2) + require.Equal(t, votes, keeper.GetVotes(ctx, proposalID)) + require.Equal(t, TestAddrs[0], votes[0].Voter) + require.Equal(t, proposalID, votes[0].ProposalID) + require.Equal(t, types.OptionYes, votes[0].Option) + require.Equal(t, TestAddrs[1], votes[1].Voter) + require.Equal(t, proposalID, votes[1].ProposalID) + require.Equal(t, types.OptionNoWithVeto, votes[1].Option) +} diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go deleted file mode 100644 index 8689bf55d..000000000 --- a/x/gov/keeper_test.go +++ /dev/null @@ -1,356 +0,0 @@ -package gov - -import ( - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestGetSetProposal(t *testing.T) { - input := getMockApp(t, 0, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - input.keeper.SetProposal(ctx, proposal) - - gotProposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - require.True(t, ProposalEqual(proposal, gotProposal)) -} - -func TestIncrementProposalNumber(t *testing.T) { - input := getMockApp(t, 0, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - - tp := testProposal() - input.keeper.SubmitProposal(ctx, tp) - input.keeper.SubmitProposal(ctx, tp) - input.keeper.SubmitProposal(ctx, tp) - input.keeper.SubmitProposal(ctx, tp) - input.keeper.SubmitProposal(ctx, tp) - proposal6, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - - require.Equal(t, uint64(6), proposal6.ProposalID) -} - -func TestActivateVotingPeriod(t *testing.T) { - input := getMockApp(t, 0, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - - require.True(t, proposal.VotingStartTime.Equal(time.Time{})) - - input.keeper.activateVotingPeriod(ctx, proposal) - - require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) - - proposal, ok := input.keeper.GetProposal(ctx, proposal.ProposalID) - require.True(t, ok) - - activeIterator := input.keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime) - require.True(t, activeIterator.Valid()) - var proposalID uint64 - input.keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) - require.Equal(t, proposalID, proposal.ProposalID) - activeIterator.Close() -} - -func TestDeposits(t *testing.T) { - input := getMockApp(t, 2, GenesisState{}, nil) - - SortAddresses(input.addrs) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - - fourStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(4))) - fiveStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromConsensusPower(5))) - - addr0Initial := input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[0]).GetCoins() - addr1Initial := input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[1]).GetCoins() - - expTokens := sdk.TokensFromConsensusPower(42) - require.Equal(t, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, expTokens)), addr0Initial) - require.True(t, proposal.TotalDeposit.IsEqual(sdk.NewCoins())) - - // Check no deposits at beginning - deposit, found := input.keeper.GetDeposit(ctx, proposalID, input.addrs[1]) - require.False(t, found) - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - require.True(t, proposal.VotingStartTime.Equal(time.Time{})) - - // Check first deposit - err, votingStarted := input.keeper.AddDeposit(ctx, proposalID, input.addrs[0], fourStake) - require.Nil(t, err) - require.False(t, votingStarted) - deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[0]) - require.True(t, found) - require.Equal(t, fourStake, deposit.Amount) - require.Equal(t, input.addrs[0], deposit.Depositor) - proposal, ok = input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - require.Equal(t, fourStake, proposal.TotalDeposit) - require.Equal(t, addr0Initial.Sub(fourStake), input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[0]).GetCoins()) - - // Check a second deposit from same address - err, votingStarted = input.keeper.AddDeposit(ctx, proposalID, input.addrs[0], fiveStake) - require.Nil(t, err) - require.False(t, votingStarted) - deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[0]) - require.True(t, found) - require.Equal(t, fourStake.Add(fiveStake), deposit.Amount) - require.Equal(t, input.addrs[0], deposit.Depositor) - proposal, ok = input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - require.Equal(t, fourStake.Add(fiveStake), proposal.TotalDeposit) - require.Equal(t, addr0Initial.Sub(fourStake).Sub(fiveStake), input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[0]).GetCoins()) - - // Check third deposit from a new address - err, votingStarted = input.keeper.AddDeposit(ctx, proposalID, input.addrs[1], fourStake) - require.Nil(t, err) - require.True(t, votingStarted) - deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[1]) - require.True(t, found) - require.Equal(t, input.addrs[1], deposit.Depositor) - require.Equal(t, fourStake, deposit.Amount) - proposal, ok = input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - require.Equal(t, fourStake.Add(fiveStake).Add(fourStake), proposal.TotalDeposit) - require.Equal(t, addr1Initial.Sub(fourStake), input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[1]).GetCoins()) - - // Check that proposal moved to voting period - proposal, ok = input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) - - // Test deposit iterator - depositsIterator := input.keeper.GetDepositsIterator(ctx, proposalID) - require.True(t, depositsIterator.Valid()) - input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) - require.Equal(t, input.addrs[0], deposit.Depositor) - require.Equal(t, fourStake.Add(fiveStake), deposit.Amount) - depositsIterator.Next() - input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) - require.Equal(t, input.addrs[1], deposit.Depositor) - require.Equal(t, fourStake, deposit.Amount) - depositsIterator.Next() - require.False(t, depositsIterator.Valid()) - depositsIterator.Close() - - // Test Refund Deposits - deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[1]) - require.True(t, found) - require.Equal(t, fourStake, deposit.Amount) - input.keeper.RefundDeposits(ctx, proposalID) - deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[1]) - require.False(t, found) - require.Equal(t, addr0Initial, input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[0]).GetCoins()) - require.Equal(t, addr1Initial, input.mApp.AccountKeeper.GetAccount(ctx, input.addrs[1]).GetCoins()) - -} - -func TestVotes(t *testing.T) { - input := getMockApp(t, 2, GenesisState{}, nil) - SortAddresses(input.addrs) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - // Test first vote - input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionAbstain) - vote, found := input.keeper.GetVote(ctx, proposalID, input.addrs[0]) - require.True(t, found) - require.Equal(t, input.addrs[0], vote.Voter) - require.Equal(t, proposalID, vote.ProposalID) - require.Equal(t, OptionAbstain, vote.Option) - - // Test change of vote - input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - vote, found = input.keeper.GetVote(ctx, proposalID, input.addrs[0]) - require.True(t, found) - require.Equal(t, input.addrs[0], vote.Voter) - require.Equal(t, proposalID, vote.ProposalID) - require.Equal(t, OptionYes, vote.Option) - - // Test second vote - input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNoWithVeto) - vote, found = input.keeper.GetVote(ctx, proposalID, input.addrs[1]) - require.True(t, found) - require.Equal(t, input.addrs[1], vote.Voter) - require.Equal(t, proposalID, vote.ProposalID) - require.Equal(t, OptionNoWithVeto, vote.Option) - - // Test vote iterator - votesIterator := input.keeper.GetVotesIterator(ctx, proposalID) - require.True(t, votesIterator.Valid()) - input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) - require.True(t, votesIterator.Valid()) - require.Equal(t, input.addrs[0], vote.Voter) - require.Equal(t, proposalID, vote.ProposalID) - require.Equal(t, OptionYes, vote.Option) - votesIterator.Next() - require.True(t, votesIterator.Valid()) - input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) - require.True(t, votesIterator.Valid()) - require.Equal(t, input.addrs[1], vote.Voter) - require.Equal(t, proposalID, vote.ProposalID) - require.Equal(t, OptionNoWithVeto, vote.Option) - votesIterator.Next() - require.False(t, votesIterator.Valid()) - votesIterator.Close() -} - -func TestProposalQueues(t *testing.T) { - input := getMockApp(t, 0, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - input.mApp.InitChainer(ctx, abci.RequestInitChain{}) - - // create test proposals - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - - inactiveIterator := input.keeper.InactiveProposalQueueIterator(ctx, proposal.DepositEndTime) - require.True(t, inactiveIterator.Valid()) - var proposalID uint64 - input.keeper.cdc.UnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) - require.Equal(t, proposalID, proposal.ProposalID) - inactiveIterator.Close() - - input.keeper.activateVotingPeriod(ctx, proposal) - - proposal, ok := input.keeper.GetProposal(ctx, proposal.ProposalID) - require.True(t, ok) - - activeIterator := input.keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime) - require.True(t, activeIterator.Valid()) - input.keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) - require.Equal(t, proposalID, proposal.ProposalID) - activeIterator.Close() -} - -type validProposal struct{} - -func (validProposal) GetTitle() string { return "title" } -func (validProposal) GetDescription() string { return "description" } -func (validProposal) ProposalRoute() string { return RouterKey } -func (validProposal) ProposalType() string { return ProposalTypeText } -func (validProposal) String() string { return "" } -func (validProposal) ValidateBasic() sdk.Error { return nil } - -type invalidProposalTitle1 struct{ validProposal } - -func (invalidProposalTitle1) GetTitle() string { return "" } - -type invalidProposalTitle2 struct{ validProposal } - -func (invalidProposalTitle2) GetTitle() string { return strings.Repeat("1234567890", 100) } - -type invalidProposalDesc1 struct{ validProposal } - -func (invalidProposalDesc1) GetDescription() string { return "" } - -type invalidProposalDesc2 struct{ validProposal } - -func (invalidProposalDesc2) GetDescription() string { return strings.Repeat("1234567890", 1000) } - -type invalidProposalRoute struct{ validProposal } - -func (invalidProposalRoute) ProposalRoute() string { return "nonexistingroute" } - -type invalidProposalValidation struct{ validProposal } - -func (invalidProposalValidation) ValidateBasic() sdk.Error { - return sdk.NewError(sdk.CodespaceUndefined, sdk.CodeInternal, "") -} - -func registerTestCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(validProposal{}, "test/validproposal", nil) - cdc.RegisterConcrete(invalidProposalTitle1{}, "test/invalidproposalt1", nil) - cdc.RegisterConcrete(invalidProposalTitle2{}, "test/invalidproposalt2", nil) - cdc.RegisterConcrete(invalidProposalDesc1{}, "test/invalidproposald1", nil) - cdc.RegisterConcrete(invalidProposalDesc2{}, "test/invalidproposald2", nil) - cdc.RegisterConcrete(invalidProposalRoute{}, "test/invalidproposalr", nil) - cdc.RegisterConcrete(invalidProposalValidation{}, "test/invalidproposalv", nil) -} - -func TestSubmitProposal(t *testing.T) { - input := getMockApp(t, 0, GenesisState{}, nil) - - registerTestCodec(input.keeper.cdc) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - input.mApp.InitChainer(ctx, abci.RequestInitChain{}) - - testCases := []struct { - content Content - expectedErr sdk.Error - }{ - {validProposal{}, nil}, - // Keeper does not check the validity of title and description, no error - {invalidProposalTitle1{}, nil}, - {invalidProposalTitle2{}, nil}, - {invalidProposalDesc1{}, nil}, - {invalidProposalDesc2{}, nil}, - // error only when invalid route - {invalidProposalRoute{}, ErrNoProposalHandlerExists(DefaultCodespace, invalidProposalRoute{})}, - // Keeper does not call ValidateBasic, msg.ValidateBasic does - {invalidProposalValidation{}, nil}, - } - - for _, tc := range testCases { - _, err := input.keeper.SubmitProposal(ctx, tc.content) - require.Equal(t, tc.expectedErr, err, "unexpected type of error: %s", err) - } -} diff --git a/x/gov/module.go b/x/gov/module.go index bd50770dc..16e304313 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -1,5 +1,7 @@ package gov +// DONTCOVER + import ( "encoding/json" @@ -23,7 +25,7 @@ var ( _ module.AppModuleBasic = AppModuleBasic{} ) -// app module basics object +// AppModuleBasic - app module basics object type AppModuleBasic struct { proposalHandlers []client.ProposalHandler // proposal handlers which live in governance cli and rest } @@ -37,32 +39,32 @@ func NewAppModuleBasic(proposalHandlers ...client.ProposalHandler) AppModuleBasi var _ module.AppModuleBasic = AppModuleBasic{} -// module name +// Name - module name func (AppModuleBasic) Name() string { return types.ModuleName } -// register module codec +// RegisterCodec -register module codec func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { RegisterCodec(cdc) } -// default genesis state +// DefaultGenesis - default genesis state func (AppModuleBasic) DefaultGenesis() json.RawMessage { - return types.ModuleCdc.MustMarshalJSON(DefaultGenesisState()) + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) } -// module validate genesis +// ValidateGenesis - module validate genesis func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { var data GenesisState - err := types.ModuleCdc.UnmarshalJSON(bz, &data) + err := ModuleCdc.UnmarshalJSON(bz, &data) if err != nil { return err } return ValidateGenesis(data) } -// register rest routes +// RegisterRESTRoutes - register rest routes func (a AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { var proposalRESTHandlers []rest.ProposalRESTHandler for _, proposalHandler := range a.proposalHandlers { @@ -72,7 +74,7 @@ func (a AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Rout rest.RegisterRoutes(ctx, rtr, proposalRESTHandlers) } -// get the root tx command of this module +// GetTxCmd gets the root tx command of this module func (a AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { var proposalCLIHandlers []*cobra.Command @@ -83,21 +85,22 @@ func (a AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { return cli.GetTxCmd(StoreKey, cdc, proposalCLIHandlers) } -// get the root query command of this module +// GetQueryCmd gets the root query command of this module func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { return cli.GetQueryCmd(StoreKey, cdc) } //___________________________ -// app module + +// AppModule - app module object type AppModule struct { AppModuleBasic keeper Keeper - supplyKeeper SupplyKeeper + supplyKeeper types.SupplyKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper, supplyKeeper SupplyKeeper) AppModule { +func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, @@ -105,54 +108,54 @@ func NewAppModule(keeper Keeper, supplyKeeper SupplyKeeper) AppModule { } } -// module name +// Name - module name func (AppModule) Name() string { - return types.ModuleName + return ModuleName } -// register invariants +// RegisterInvariants registers module invariants func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { RegisterInvariants(ir, am.keeper) } -// module message route name +// Route - module message route name func (AppModule) Route() string { return RouterKey } -// module handler +// NewHandler - module handler func (am AppModule) NewHandler() sdk.Handler { return NewHandler(am.keeper) } -// module querier route name +// QuerierRoute - module querier route name func (AppModule) QuerierRoute() string { return QuerierRoute } -// module querier +// NewQuerierHandler - module querier func (am AppModule) NewQuerierHandler() sdk.Querier { return NewQuerier(am.keeper) } -// module init-genesis +// InitGenesis - module init-genesis func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { var genesisState GenesisState - types.ModuleCdc.MustUnmarshalJSON(data, &genesisState) + ModuleCdc.MustUnmarshalJSON(data, &genesisState) InitGenesis(ctx, am.keeper, am.supplyKeeper, genesisState) return []abci.ValidatorUpdate{} } -// module export genesis +// ExportGenesis - module export genesis func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { gs := ExportGenesis(ctx, am.keeper) - return types.ModuleCdc.MustMarshalJSON(gs) + return ModuleCdc.MustMarshalJSON(gs) } -// module begin-block +// BeginBlock - module begin-block func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} -// module end-block +// EndBlock - module end-block func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { EndBlocker(ctx, am.keeper) return []abci.ValidatorUpdate{} diff --git a/x/gov/querier_test.go b/x/gov/querier_test.go deleted file mode 100644 index 86f804b22..000000000 --- a/x/gov/querier_test.go +++ /dev/null @@ -1,302 +0,0 @@ -package gov - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/gov/types" -) - -const custom = "custom" - -func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (DepositParams, VotingParams, TallyParams) { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryParams, ParamDeposit}, "/"), - Data: []byte{}, - } - - bz, err := querier(ctx, []string{QueryParams, ParamDeposit}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var depositParams DepositParams - err2 := cdc.UnmarshalJSON(bz, &depositParams) - require.Nil(t, err2) - - query = abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryParams, ParamVoting}, "/"), - Data: []byte{}, - } - - bz, err = querier(ctx, []string{QueryParams, ParamVoting}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var votingParams VotingParams - err2 = cdc.UnmarshalJSON(bz, &votingParams) - require.Nil(t, err2) - - query = abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryParams, ParamTallying}, "/"), - Data: []byte{}, - } - - bz, err = querier(ctx, []string{QueryParams, ParamTallying}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var tallyParams TallyParams - err2 = cdc.UnmarshalJSON(bz, &tallyParams) - require.Nil(t, err2) - - return depositParams, votingParams, tallyParams -} - -func getQueriedProposal(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) Proposal { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryProposal}, "/"), - Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), - } - - bz, err := querier(ctx, []string{QueryProposal}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var proposal Proposal - err2 := cdc.UnmarshalJSON(bz, proposal) - require.Nil(t, err2) - return proposal -} - -func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, depositor, voter sdk.AccAddress, status ProposalStatus, limit uint64) []Proposal { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryProposals}, "/"), - Data: cdc.MustMarshalJSON(NewQueryProposalsParams(status, limit, voter, depositor)), - } - - bz, err := querier(ctx, []string{QueryProposals}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var proposals Proposals - err2 := cdc.UnmarshalJSON(bz, &proposals) - require.Nil(t, err2) - return proposals -} - -func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, depositor sdk.AccAddress) Deposit { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryDeposit}, "/"), - Data: cdc.MustMarshalJSON(NewQueryDepositParams(proposalID, depositor)), - } - - bz, err := querier(ctx, []string{QueryDeposit}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var deposit Deposit - err2 := cdc.UnmarshalJSON(bz, &deposit) - require.Nil(t, err2) - return deposit -} - -func getQueriedDeposits(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []Deposit { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryDeposits}, "/"), - Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), - } - - bz, err := querier(ctx, []string{QueryDeposits}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var deposits []Deposit - err2 := cdc.UnmarshalJSON(bz, &deposits) - require.Nil(t, err2) - return deposits -} - -func getQueriedVote(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, voter sdk.AccAddress) Vote { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryVote}, "/"), - Data: cdc.MustMarshalJSON(NewQueryVoteParams(proposalID, voter)), - } - - bz, err := querier(ctx, []string{QueryVote}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var vote Vote - err2 := cdc.UnmarshalJSON(bz, &vote) - require.Nil(t, err2) - return vote -} - -func getQueriedVotes(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []Vote { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryVote}, "/"), - Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), - } - - bz, err := querier(ctx, []string{QueryVotes}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var votes []Vote - err2 := cdc.UnmarshalJSON(bz, &votes) - require.Nil(t, err2) - return votes -} - -func getQueriedTally(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) TallyResult { - query := abci.RequestQuery{ - Path: strings.Join([]string{custom, QuerierRoute, QueryTally}, "/"), - Data: cdc.MustMarshalJSON(NewQueryProposalParams(proposalID)), - } - - bz, err := querier(ctx, []string{QueryTally}, query) - require.Nil(t, err) - require.NotNil(t, bz) - - var tally TallyResult - err2 := cdc.UnmarshalJSON(bz, &tally) - require.Nil(t, err2) - return tally -} - -func TestQueryParams(t *testing.T) { - cdc := codec.New() - input := getMockApp(t, 1000, GenesisState{}, nil) - querier := NewQuerier(input.keeper) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.NewContext(false, abci.Header{}) - - getQueriedParams(t, ctx, cdc, querier) -} - -func TestQueries(t *testing.T) { - cdc := codec.New() - input := getMockApp(t, 1000, GenesisState{}, nil) - querier := NewQuerier(input.keeper) - handler := NewHandler(input.keeper) - - types.RegisterCodec(cdc) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.NewContext(false, abci.Header{}) - - depositParams, _, _ := getQueriedParams(t, ctx, cdc, querier) - - // input.addrs[0] proposes (and deposits) proposals #1 and #2 - res := handler(ctx, NewMsgSubmitProposal(testProposal(), sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, input.addrs[0])) - var proposalID1 uint64 - require.True(t, res.IsOK()) - cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID1) - - res = handler(ctx, NewMsgSubmitProposal(testProposal(), sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 10000000)}, input.addrs[0])) - var proposalID2 uint64 - require.True(t, res.IsOK()) - cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID2) - - // input.addrs[1] proposes (and deposits) proposals #3 - res = handler(ctx, NewMsgSubmitProposal(testProposal(), sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, input.addrs[1])) - var proposalID3 uint64 - require.True(t, res.IsOK()) - cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID3) - - // input.addrs[1] deposits on proposals #2 & #3 - res = handler(ctx, NewMsgDeposit(input.addrs[1], proposalID2, depositParams.MinDeposit)) - res = handler(ctx, NewMsgDeposit(input.addrs[1], proposalID3, depositParams.MinDeposit)) - - // check deposits on proposal1 match individual deposits - deposits := getQueriedDeposits(t, ctx, cdc, querier, proposalID1) - require.Len(t, deposits, 1) - deposit := getQueriedDeposit(t, ctx, cdc, querier, proposalID1, input.addrs[0]) - require.Equal(t, deposit, deposits[0]) - - // check deposits on proposal2 match individual deposits - deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID2) - require.Len(t, deposits, 2) - deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, input.addrs[0]) - require.True(t, deposit.Equals(deposits[0])) - deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, input.addrs[1]) - require.True(t, deposit.Equals(deposits[1])) - - // check deposits on proposal3 match individual deposits - deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID3) - require.Len(t, deposits, 1) - deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID3, input.addrs[1]) - require.Equal(t, deposit, deposits[0]) - - // Only proposal #1 should be in Deposit Period - proposals := getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusDepositPeriod, 0) - require.Len(t, proposals, 1) - require.Equal(t, proposalID1, proposals[0].ProposalID) - - // Only proposals #2 and #3 should be in Voting Period - proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusVotingPeriod, 0) - require.Len(t, proposals, 2) - require.Equal(t, proposalID2, proposals[0].ProposalID) - require.Equal(t, proposalID3, proposals[1].ProposalID) - - // Addrs[0] votes on proposals #2 & #3 - require.True(t, handler(ctx, NewMsgVote(input.addrs[0], proposalID2, OptionYes)).IsOK()) - require.True(t, handler(ctx, NewMsgVote(input.addrs[0], proposalID3, OptionYes)).IsOK()) - - // Addrs[1] votes on proposal #3 - handler(ctx, NewMsgVote(input.addrs[1], proposalID3, OptionYes)) - - // Test query voted by input.addrs[0] - proposals = getQueriedProposals(t, ctx, cdc, querier, nil, input.addrs[0], StatusNil, 0) - require.Equal(t, proposalID2, (proposals[0]).ProposalID) - require.Equal(t, proposalID3, (proposals[1]).ProposalID) - - // Test query votes on Proposal 2 - votes := getQueriedVotes(t, ctx, cdc, querier, proposalID2) - require.Len(t, votes, 1) - require.Equal(t, input.addrs[0], votes[0].Voter) - - vote := getQueriedVote(t, ctx, cdc, querier, proposalID2, input.addrs[0]) - require.Equal(t, vote, votes[0]) - - // Test query votes on Proposal 3 - votes = getQueriedVotes(t, ctx, cdc, querier, proposalID3) - require.Len(t, votes, 2) - require.True(t, input.addrs[0].String() == votes[0].Voter.String()) - require.True(t, input.addrs[1].String() == votes[1].Voter.String()) - - // Test proposals queries with filters - - // Test query all proposals - proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusNil, 0) - require.Equal(t, proposalID1, (proposals[0]).ProposalID) - require.Equal(t, proposalID2, (proposals[1]).ProposalID) - require.Equal(t, proposalID3, (proposals[2]).ProposalID) - - // Test query voted by input.addrs[1] - proposals = getQueriedProposals(t, ctx, cdc, querier, nil, input.addrs[1], StatusNil, 0) - require.Equal(t, proposalID3, (proposals[0]).ProposalID) - - // Test query deposited by input.addrs[0] - proposals = getQueriedProposals(t, ctx, cdc, querier, input.addrs[0], nil, StatusNil, 0) - require.Equal(t, proposalID1, (proposals[0]).ProposalID) - - // Test query deposited by addr2 - proposals = getQueriedProposals(t, ctx, cdc, querier, input.addrs[1], nil, StatusNil, 0) - require.Equal(t, proposalID2, (proposals[0]).ProposalID) - require.Equal(t, proposalID3, (proposals[1]).ProposalID) - - // Test query voted AND deposited by addr1 - proposals = getQueriedProposals(t, ctx, cdc, querier, input.addrs[0], input.addrs[0], StatusNil, 0) - require.Equal(t, proposalID2, (proposals[0]).ProposalID) -} diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go deleted file mode 100644 index 8cc70301e..000000000 --- a/x/gov/tally_test.go +++ /dev/null @@ -1,604 +0,0 @@ -package gov - -import ( - "testing" - - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking" -) - -func TestTallyNoOneVotes(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:2])) - for i, addr := range input.addrs[:2] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.True(t, burnDeposits) - require.True(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyNoQuorum(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:2])) - for i, addr := range input.addrs[:2] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{2, 5}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, _ := tally(ctx, input.keeper, proposal) - require.False(t, passes) - require.True(t, burnDeposits) -} - -func TestTallyOnlyValidatorsAllYes(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:2])) - for i, addr := range input.addrs[:2] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.True(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyOnlyValidators51No(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:2])) - for i, addr := range input.addrs[:2] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, _ := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.False(t, burnDeposits) -} - -func TestTallyOnlyValidators51Yes(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.True(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyOnlyValidatorsVetoed(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNoWithVeto) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.True(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) - -} - -func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionAbstain) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.True(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionAbstain) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyOnlyValidatorsNonVoter(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyDelgatorOverride(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - delTokens := sdk.TokensFromConsensusPower(30) - delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[3], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyDelgatorInherit(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - delTokens := sdk.TokensFromConsensusPower(30) - delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionNo) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.True(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyDelgatorMultipleOverride(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - delTokens := sdk.TokensFromConsensusPower(10) - delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg) - delegator1Msg2 := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg2) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[3], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyDelgatorMultipleInherit(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valTokens1 := sdk.TokensFromConsensusPower(25) - val1CreateMsg := staking.NewMsgCreateValidator( - sdk.ValAddress(input.addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens1), testDescription, testCommissionRates, sdk.OneInt(), - ) - stakingHandler(ctx, val1CreateMsg) - - valTokens2 := sdk.TokensFromConsensusPower(6) - val2CreateMsg := staking.NewMsgCreateValidator( - sdk.ValAddress(input.addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens2), testDescription, testCommissionRates, sdk.OneInt(), - ) - stakingHandler(ctx, val2CreateMsg) - - valTokens3 := sdk.TokensFromConsensusPower(7) - val3CreateMsg := staking.NewMsgCreateValidator( - sdk.ValAddress(input.addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens3), testDescription, testCommissionRates, sdk.OneInt(), - ) - stakingHandler(ctx, val3CreateMsg) - - delTokens := sdk.TokensFromConsensusPower(10) - delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg) - - delegator1Msg2 := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg2) - - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.False(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} - -func TestTallyJailedValidator(t *testing.T) { - input := getMockApp(t, 10, GenesisState{}, nil) - - header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} - input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - - ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) - stakingHandler := staking.NewHandler(input.sk) - - valAddrs := make([]sdk.ValAddress, len(input.addrs[:3])) - for i, addr := range input.addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakingHandler, ctx, valAddrs, []int64{25, 6, 7}) - staking.EndBlocker(ctx, input.sk) - - delTokens := sdk.TokensFromConsensusPower(10) - delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg) - - delegator1Msg2 := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens)) - stakingHandler(ctx, delegator1Msg2) - - val2, found := input.sk.GetValidator(ctx, sdk.ValAddress(input.addrs[1])) - require.True(t, found) - input.sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address())) - - staking.EndBlocker(ctx, input.sk) - - tp := testProposal() - proposal, err := input.keeper.SubmitProposal(ctx, tp) - require.NoError(t, err) - proposalID := proposal.ProposalID - proposal.Status = StatusVotingPeriod - input.keeper.SetProposal(ctx, proposal) - - err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo) - require.Nil(t, err) - err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo) - require.Nil(t, err) - - proposal, ok := input.keeper.GetProposal(ctx, proposalID) - require.True(t, ok) - passes, burnDeposits, tallyResults := tally(ctx, input.keeper, proposal) - - require.True(t, passes) - require.False(t, burnDeposits) - require.False(t, tallyResults.Equals(EmptyTallyResult())) -} diff --git a/x/gov/test_common.go b/x/gov/test_common.go index 63ac76f19..4462f0cda 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -1,4 +1,5 @@ -// nolint:deadcode unused +// nolint +// DONTCOVER package gov import ( @@ -17,6 +18,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + keep "github.com/cosmos/cosmos-sdk/x/gov/keeper" "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/staking" @@ -33,15 +35,16 @@ var ( type testInput struct { mApp *mock.App - keeper Keeper - router Router + keeper keep.Keeper + router types.Router sk staking.Keeper addrs []sdk.AccAddress pubKeys []crypto.PubKey privKeys []crypto.PrivKey } -func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []auth.Account) testInput { +func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAccs []auth.Account, + handler func(ctx sdk.Context, c types.Content) sdk.Error) testInput { mApp := mock.NewApp() staking.RegisterCodec(mApp.Cdc) @@ -50,7 +53,7 @@ func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []a keyStaking := sdk.NewKVStoreKey(staking.StoreKey) tKeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) - keyGov := sdk.NewKVStoreKey(StoreKey) + keyGov := sdk.NewKVStoreKey(types.StoreKey) keySupply := sdk.NewKVStoreKey(supply.StoreKey) govAcc := supply.NewEmptyModuleAccount(types.ModuleName, supply.Burner) @@ -58,14 +61,14 @@ func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []a bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[govAcc.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true + blacklistedAddrs[govAcc.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true pk := mApp.ParamsKeeper - rtr := NewRouter(). - AddRoute(RouterKey, ProposalHandler) + rtr := types.NewRouter(). + AddRoute(types.RouterKey, handler) bk := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) @@ -77,10 +80,11 @@ func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []a supplyKeeper := supply.NewKeeper(mApp.Cdc, keySupply, mApp.AccountKeeper, bk, maccPerms) sk := staking.NewKeeper(mApp.Cdc, keyStaking, tKeyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) - keeper := NewKeeper(mApp.Cdc, keyGov, pk, pk.Subspace(DefaultParamspace), supplyKeeper, sk, DefaultCodespace, rtr) + keeper := keep.NewKeeper(mApp.Cdc, keyGov, pk.Subspace(DefaultParamspace).WithKeyTable(ParamKeyTable()), + supplyKeeper, sk, types.DefaultCodespace, rtr) - mApp.Router().AddRoute(RouterKey, NewHandler(keeper)) - mApp.QueryRouter().AddRoute(QuerierRoute, NewQuerier(keeper)) + mApp.Router().AddRoute(types.RouterKey, NewHandler(keeper)) + mApp.QueryRouter().AddRoute(types.QuerierRoute, keep.NewQuerier(keeper)) mApp.SetEndBlocker(getEndBlocker(keeper)) mApp.SetInitChainer(getInitChainer(mApp, keeper, sk, supplyKeeper, genAccs, genState, @@ -129,7 +133,7 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, validators := staking.InitGenesis(ctx, stakingKeeper, mapp.AccountKeeper, supplyKeeper, stakingGenesis) if genState.IsEmpty() { - InitGenesis(ctx, keeper, supplyKeeper, DefaultGenesisState()) + InitGenesis(ctx, keeper, supplyKeeper, types.DefaultGenesisState()) } else { InitGenesis(ctx, keeper, supplyKeeper, genState) } @@ -139,7 +143,7 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, } } -// Sorts Addresses +// SortAddresses - Sorts Addresses func SortAddresses(addrs []sdk.AccAddress) { var byteAddrs [][]byte for _, addr := range addrs { @@ -175,25 +179,21 @@ func (b sortByteArrays) Swap(i, j int) { b[j], b[i] = b[i], b[j] } -// Public +// SortByteArrays - sorts the provided byte array func SortByteArrays(src [][]byte) [][]byte { sorted := sortByteArrays(src) sort.Sort(sorted) return sorted } -func testProposal() Content { - return NewTextProposal("Test", "description") -} - const contextKeyBadProposal = "contextKeyBadProposal" // badProposalHandler implements a governance proposal handler that is identical // to the actual handler except this fails if the context doesn't contain a value // for the key contextKeyBadProposal or if the value is false. -func badProposalHandler(ctx sdk.Context, c Content) sdk.Error { +func badProposalHandler(ctx sdk.Context, c types.Content) sdk.Error { switch c.ProposalType() { - case ProposalTypeText, ProposalTypeSoftwareUpgrade: + case types.ProposalTypeText, types.ProposalTypeSoftwareUpgrade: v := ctx.Value(contextKeyBadProposal) if v == nil || !v.(bool) { @@ -203,26 +203,17 @@ func badProposalHandler(ctx sdk.Context, c Content) sdk.Error { return nil default: - errMsg := fmt.Sprintf("unrecognized gov proposal type: %s", c.ProposalType()) - return sdk.ErrUnknownRequest(errMsg) + msg := fmt.Sprintf("unrecognized gov proposal type: %s", c.ProposalType()) + return sdk.ErrUnknownRequest(msg) } } -// checks if two proposals are equal (note: slow, for tests only) -func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { - return bytes.Equal(types.ModuleCdc.MustMarshalBinaryBare(proposalA), - types.ModuleCdc.MustMarshalBinaryBare(proposalB)) -} - var ( pubkeys = []crypto.PubKey{ ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey(), } - - testDescription = staking.NewDescription("T", "E", "S", "T") - testCommissionRates = staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) func createValidators(t *testing.T, stakingHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, powerAmt []int64) { @@ -233,7 +224,7 @@ func createValidators(t *testing.T, stakingHandler sdk.Handler, ctx sdk.Context, valTokens := sdk.TokensFromConsensusPower(powerAmt[i]) valCreateMsg := staking.NewMsgCreateValidator( addrs[i], pubkeys[i], sdk.NewCoin(sdk.DefaultBondDenom, valTokens), - testDescription, testCommissionRates, sdk.OneInt(), + keep.TestDescription, keep.TestCommissionRates, sdk.OneInt(), ) res := stakingHandler(ctx, valCreateMsg) diff --git a/x/gov/types/deposit.go b/x/gov/types/deposit.go index 2ec06e8e8..4d9ff9304 100644 --- a/x/gov/types/deposit.go +++ b/x/gov/types/deposit.go @@ -6,7 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Deposit +// Deposit defines an amount deposited by an account address to an active proposal type Deposit struct { ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"` // proposalID of the proposal Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor diff --git a/x/gov/types/errors.go b/x/gov/types/errors.go index 65b97d908..27102c7c5 100644 --- a/x/gov/types/errors.go +++ b/x/gov/types/errors.go @@ -1,14 +1,16 @@ -//nolint package types +// DONTCOVER + import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) +// Codes for governance errors const ( - DefaultCodespace sdk.CodespaceType = "gov" + DefaultCodespace sdk.CodespaceType = ModuleName CodeUnknownProposal sdk.CodeType = 1 CodeInactiveProposal sdk.CodeType = 2 @@ -23,42 +25,42 @@ const ( CodeProposalHandlerNotExists sdk.CodeType = 11 ) +// ErrUnknownProposal error for unknown proposals func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("unknown proposal with id %d", proposalID)) } +// ErrInactiveProposal error for inactive (i.e finalized) proposals func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("inactive proposal with id %d", proposalID)) } +// ErrAlreadyActiveProposal error for proposals that are already active func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("proposal %d has been already active", proposalID)) } -func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { - return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("proposal %d has already passed its voting period", proposalID)) -} - -func ErrAddressNotStaked(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error { - return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("address %s is not staked and is thus ineligible to vote", address)) -} - +// ErrInvalidProposalContent error for invalid proposal title or description func ErrInvalidProposalContent(cs sdk.CodespaceType, msg string) sdk.Error { return sdk.NewError(cs, CodeInvalidContent, fmt.Sprintf("invalid proposal content: %s", msg)) } +// ErrInvalidProposalType error for non registered proposal types func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType string) sdk.Error { return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("proposal type '%s' is not valid", proposalType)) } +// ErrInvalidVote error for an invalid vote option func ErrInvalidVote(codespace sdk.CodespaceType, voteOption VoteOption) sdk.Error { return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("'%v' is not a valid voting option", voteOption.String())) } +// ErrInvalidGenesis error for an invalid governance GenesisState func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error { return sdk.NewError(codespace, CodeInvalidVote, msg) } +// ErrNoProposalHandlerExists error when proposal handler is not defined func ErrNoProposalHandlerExists(codespace sdk.CodespaceType, content interface{}) sdk.Error { return sdk.NewError(codespace, CodeProposalHandlerNotExists, fmt.Sprintf("'%T' does not have a corresponding handler", content)) } diff --git a/x/gov/expected_keepers.go b/x/gov/types/expected_keepers.go similarity index 80% rename from x/gov/expected_keepers.go rename to x/gov/types/expected_keepers.go index f53a49527..8b28faea8 100644 --- a/x/gov/expected_keepers.go +++ b/x/gov/types/expected_keepers.go @@ -1,4 +1,4 @@ -package gov +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -6,7 +6,13 @@ import ( supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" ) -// SupplyKeeper defines the supply Keeper for module accounts +// ParamSubspace defines the expected Subspace interface for parameters (noalias) +type ParamSubspace interface { + Get(ctx sdk.Context, key []byte, ptr interface{}) + Set(ctx sdk.Context, key []byte, param interface{}) +} + +// SupplyKeeper defines the expected supply keeper for module accounts (noalias) type SupplyKeeper interface { GetModuleAddress(name string) sdk.AccAddress GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI @@ -19,7 +25,7 @@ type SupplyKeeper interface { BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error } -// StakingKeeper expected staking keeper (Validator and Delegator sets) +// StakingKeeper expected staking keeper (Validator and Delegator sets) (noalias) type StakingKeeper interface { // iterate through bonded validators by operator address, execute func for each validator IterateBondedValidatorsByPower(sdk.Context, diff --git a/x/gov/types/genesis.go b/x/gov/types/genesis.go new file mode 100644 index 000000000..165606bc4 --- /dev/null +++ b/x/gov/types/genesis.go @@ -0,0 +1,73 @@ +package types + +import ( + "bytes" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all staking state that must be provided at genesis +type GenesisState struct { + StartingProposalID uint64 `json:"starting_proposal_id" yaml:"starting_proposal_id"` + Deposits Deposits `json:"deposits" yaml:"deposits"` + Votes Votes `json:"votes" yaml:"votes"` + Proposals Proposals `json:"proposals" yaml:"proposals"` + DepositParams DepositParams `json:"deposit_params" yaml:"deposit_params"` + VotingParams VotingParams `json:"voting_params" yaml:"voting_params"` + TallyParams TallyParams `json:"tally_params" yaml:"tally_params"` +} + +// NewGenesisState creates a new genesis state for the governance module +func NewGenesisState(startingProposalID uint64, dp DepositParams, vp VotingParams, tp TallyParams) GenesisState { + return GenesisState{ + StartingProposalID: startingProposalID, + DepositParams: dp, + VotingParams: vp, + TallyParams: tp, + } +} + +// DefaultGenesisState defines the default governance genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState( + DefaultStartingProposalID, + DefaultDepositParams(), + DefaultVotingParams(), + DefaultTallyParams(), + ) +} + +// Equal checks whether two gov GenesisState structs are equivalent +func (data GenesisState) Equal(data2 GenesisState) bool { + b1 := ModuleCdc.MustMarshalBinaryBare(data) + b2 := ModuleCdc.MustMarshalBinaryBare(data2) + return bytes.Equal(b1, b2) +} + +// IsEmpty returns true if a GenesisState is empty +func (data GenesisState) IsEmpty() bool { + return data.Equal(GenesisState{}) +} + +// ValidateGenesis checks if parameters are within valid ranges +func ValidateGenesis(data GenesisState) error { + threshold := data.TallyParams.Threshold + if threshold.IsNegative() || threshold.GT(sdk.OneDec()) { + return fmt.Errorf("Governance vote threshold should be positive and less or equal to one, is %s", + threshold.String()) + } + + veto := data.TallyParams.Veto + if veto.IsNegative() || veto.GT(sdk.OneDec()) { + return fmt.Errorf("Governance vote veto threshold should be positive and less or equal to one, is %s", + veto.String()) + } + + if !data.DepositParams.MinDeposit.IsValid() { + return fmt.Errorf("Governance deposit amount must be a valid sdk.Coins amount, is %s", + data.DepositParams.MinDeposit.String()) + } + + return nil +} diff --git a/x/gov/types/genesis_test.go b/x/gov/types/genesis_test.go new file mode 100644 index 000000000..480c3cb5d --- /dev/null +++ b/x/gov/types/genesis_test.go @@ -0,0 +1,22 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEqualProposalID(t *testing.T) { + state1 := GenesisState{} + state2 := GenesisState{} + require.Equal(t, state1, state2) + + // Proposals + state1.StartingProposalID = 1 + require.NotEqual(t, state1, state2) + require.False(t, state1.Equal(state2)) + + state2.StartingProposalID = 1 + require.Equal(t, state1, state2) + require.True(t, state1.Equal(state2)) +} \ No newline at end of file diff --git a/x/gov/types/keys.go b/x/gov/types/keys.go index 41f81c2fb..98f6f0d88 100644 --- a/x/gov/types/keys.go +++ b/x/gov/types/keys.go @@ -52,11 +52,21 @@ var ( var lenTime = len(sdk.FormatTimeBytes(time.Now())) +// GetProposalIDBytes returns the byte representation of the proposalID +func GetProposalIDBytes(proposalID uint64) (proposalIDBz []byte) { + proposalIDBz = make([]byte, 8) + binary.BigEndian.PutUint64(proposalIDBz, proposalID) + return +} + +// GetProposalIDFromBytes returns proposalID in uint64 format from a byte array +func GetProposalIDFromBytes(bz []byte) (proposalID uint64) { + return binary.BigEndian.Uint64(bz) +} + // ProposalKey gets a specific proposal from the store func ProposalKey(proposalID uint64) []byte { - bz := make([]byte, 8) - binary.LittleEndian.PutUint64(bz, proposalID) - return append(ProposalsKeyPrefix, bz...) + return append(ProposalsKeyPrefix, GetProposalIDBytes(proposalID)...) } // ActiveProposalByTimeKey gets the active proposal queue key by endTime @@ -66,10 +76,7 @@ func ActiveProposalByTimeKey(endTime time.Time) []byte { // ActiveProposalQueueKey returns the key for a proposalID in the activeProposalQueue func ActiveProposalQueueKey(proposalID uint64, endTime time.Time) []byte { - bz := make([]byte, 8) - binary.LittleEndian.PutUint64(bz, proposalID) - - return append(ActiveProposalByTimeKey(endTime), bz...) + return append(ActiveProposalByTimeKey(endTime), GetProposalIDBytes(proposalID)...) } // InactiveProposalByTimeKey gets the inactive proposal queue key by endTime @@ -79,17 +86,12 @@ func InactiveProposalByTimeKey(endTime time.Time) []byte { // InactiveProposalQueueKey returns the key for a proposalID in the inactiveProposalQueue func InactiveProposalQueueKey(proposalID uint64, endTime time.Time) []byte { - bz := make([]byte, 8) - binary.LittleEndian.PutUint64(bz, proposalID) - - return append(InactiveProposalByTimeKey(endTime), bz...) + return append(InactiveProposalByTimeKey(endTime), GetProposalIDBytes(proposalID)...) } // DepositsKey gets the first part of the deposits key based on the proposalID func DepositsKey(proposalID uint64) []byte { - bz := make([]byte, 8) - binary.LittleEndian.PutUint64(bz, proposalID) - return append(DepositsKeyPrefix, bz...) + return append(DepositsKeyPrefix, GetProposalIDBytes(proposalID)...) } // DepositKey key of a specific deposit from the store @@ -99,9 +101,7 @@ func DepositKey(proposalID uint64, depositorAddr sdk.AccAddress) []byte { // VotesKey gets the first part of the votes key based on the proposalID func VotesKey(proposalID uint64) []byte { - bz := make([]byte, 8) - binary.LittleEndian.PutUint64(bz, proposalID) - return append(VotesKeyPrefix, bz...) + return append(VotesKeyPrefix, GetProposalIDBytes(proposalID)...) } // VoteKey key of a specific vote from the store @@ -117,7 +117,7 @@ func SplitProposalKey(key []byte) (proposalID uint64) { panic(fmt.Sprintf("unexpected key length (%d ≠ 8)", len(key[1:]))) } - return binary.LittleEndian.Uint64(key[1:]) + return GetProposalIDFromBytes(key[1:]) } // SplitActiveProposalQueueKey split the active proposal key and returns the proposal id and endTime @@ -151,7 +151,8 @@ func splitKeyWithTime(key []byte) (proposalID uint64, endTime time.Time) { if err != nil { panic(err) } - proposalID = binary.LittleEndian.Uint64(key[1+lenTime:]) + + proposalID = GetProposalIDFromBytes(key[1+lenTime:]) return } @@ -160,7 +161,7 @@ func splitKeyWithAddress(key []byte) (proposalID uint64, addr sdk.AccAddress) { panic(fmt.Sprintf("unexpected key length (%d ≠ %d)", len(key), 8+sdk.AddrLen)) } - proposalID = binary.LittleEndian.Uint64(key[1:9]) + proposalID = GetProposalIDFromBytes(key[1:9]) addr = sdk.AccAddress(key[9:]) return } diff --git a/x/gov/types/msgs.go b/x/gov/types/msgs.go index 57fc2e0ce..aead43007 100644 --- a/x/gov/types/msgs.go +++ b/x/gov/types/msgs.go @@ -15,22 +15,26 @@ const ( var _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgDeposit{}, MsgVote{} -// MsgSubmitProposal +// MsgSubmitProposal defines a message to create a governance proposal with a +// given content and initial deposit type MsgSubmitProposal struct { Content Content `json:"content" yaml:"content"` InitialDeposit sdk.Coins `json:"initial_deposit" yaml:"initial_deposit"` // Initial deposit paid by sender. Must be strictly positive Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"` // Address of the proposer } +// NewMsgSubmitProposal creates a new MsgSubmitProposal instance func NewMsgSubmitProposal(content Content, initialDeposit sdk.Coins, proposer sdk.AccAddress) MsgSubmitProposal { return MsgSubmitProposal{content, initialDeposit, proposer} } -//nolint +// Route implements Msg func (msg MsgSubmitProposal) Route() string { return RouterKey } -func (msg MsgSubmitProposal) Type() string { return TypeMsgSubmitProposal } -// Implements Msg. +// Type implements Msg +func (msg MsgSubmitProposal) Type() string { return TypeMsgSubmitProposal } + +// ValidateBasic implements Msg func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { if msg.Content == nil { return ErrInvalidProposalContent(DefaultCodespace, "missing content") @@ -57,6 +61,7 @@ func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { return msg.Content.ValidateBasic() } +// String implements the Stringer interface func (msg MsgSubmitProposal) String() string { return fmt.Sprintf(`Submit Proposal Message: Content: %s @@ -64,34 +69,36 @@ func (msg MsgSubmitProposal) String() string { `, msg.Content.String(), msg.InitialDeposit) } -// Implements Msg. +// GetSignBytes implements Msg func (msg MsgSubmitProposal) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } -// Implements Msg. +// GetSigners implements Msg func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Proposer} } -// MsgDeposit +// MsgDeposit defines a message to submit a deposit to an existing proposal type MsgDeposit struct { ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"` // ID of the proposal Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor Amount sdk.Coins `json:"amount" yaml:"amount"` // Coins to add to the proposal's deposit } +// NewMsgDeposit creates a new MsgDeposit instance func NewMsgDeposit(depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins) MsgDeposit { return MsgDeposit{proposalID, depositor, amount} } -// Implements Msg. -// nolint +// Route implements Msg func (msg MsgDeposit) Route() string { return RouterKey } -func (msg MsgDeposit) Type() string { return TypeMsgDeposit } -// Implements Msg. +// Type implements Msg +func (msg MsgDeposit) Type() string { return TypeMsgDeposit } + +// ValidateBasic implements Msg func (msg MsgDeposit) ValidateBasic() sdk.Error { if msg.Depositor.Empty() { return sdk.ErrInvalidAddress(msg.Depositor.String()) @@ -106,6 +113,7 @@ func (msg MsgDeposit) ValidateBasic() sdk.Error { return nil } +// String implements the Stringer interface func (msg MsgDeposit) String() string { return fmt.Sprintf(`Deposit Message: Depositer: %s @@ -114,34 +122,36 @@ func (msg MsgDeposit) String() string { `, msg.Depositor, msg.ProposalID, msg.Amount) } -// Implements Msg. +// GetSignBytes implements Msg func (msg MsgDeposit) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } -// Implements Msg. +// GetSigners implements Msg func (msg MsgDeposit) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Depositor} } -// MsgVote +// MsgVote defines a message to cast a vote type MsgVote struct { ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"` // ID of the proposal Voter sdk.AccAddress `json:"voter" yaml:"voter"` // address of the voter Option VoteOption `json:"option" yaml:"option"` // option from OptionSet chosen by the voter } +// NewMsgVote creates a message to cast a vote on an active proposal func NewMsgVote(voter sdk.AccAddress, proposalID uint64, option VoteOption) MsgVote { return MsgVote{proposalID, voter, option} } -// Implements Msg. -// nolint +// Route implements Msg func (msg MsgVote) Route() string { return RouterKey } -func (msg MsgVote) Type() string { return TypeMsgVote } -// Implements Msg. +// Type implements Msg +func (msg MsgVote) Type() string { return TypeMsgVote } + +// ValidateBasic implements Msg func (msg MsgVote) ValidateBasic() sdk.Error { if msg.Voter.Empty() { return sdk.ErrInvalidAddress(msg.Voter.String()) @@ -153,6 +163,7 @@ func (msg MsgVote) ValidateBasic() sdk.Error { return nil } +// String implements the Stringer interface func (msg MsgVote) String() string { return fmt.Sprintf(`Vote Message: Proposal ID: %d @@ -160,13 +171,13 @@ func (msg MsgVote) String() string { `, msg.ProposalID, msg.Option) } -// Implements Msg. +// GetSignBytes implements Msg func (msg MsgVote) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } -// Implements Msg. +// GetSigners implements Msg func (msg MsgVote) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Voter} } diff --git a/x/gov/types/params.go b/x/gov/types/params.go index 9ae33e75a..a7fe1da7b 100644 --- a/x/gov/types/params.go +++ b/x/gov/types/params.go @@ -8,6 +8,19 @@ import ( params "github.com/cosmos/cosmos-sdk/x/params/subspace" ) +// Default period for deposits & voting +const ( + DefaultPeriod time.Duration = time.Hour * 24 * 2 // 2 days +) + +// Default governance params +var ( + DefaultMinDepositTokens = sdk.TokensFromConsensusPower(10) + DefaultQuorum = sdk.NewDecWithPrec(334, 3) + DefaultThreshold = sdk.NewDecWithPrec(5, 1) + DefaultVeto = sdk.NewDecWithPrec(334, 3) +) + // Parameter store key var ( ParamStoreKeyDepositParams = []byte("depositparams") @@ -15,7 +28,7 @@ var ( ParamStoreKeyTallyParams = []byte("tallyparams") ) -// Key declaration for parameters +// ParamKeyTable - Key declaration for parameters func ParamKeyTable() params.KeyTable { return params.NewKeyTable( ParamStoreKeyDepositParams, DepositParams{}, @@ -24,7 +37,7 @@ func ParamKeyTable() params.KeyTable { ) } -// Param around deposits for governance +// DepositParams defines the params around deposits for governance type DepositParams struct { MinDeposit sdk.Coins `json:"min_deposit,omitempty" yaml:"min_deposit,omitempty"` // Minimum deposit for a proposal to enter voting period. MaxDepositPeriod time.Duration `json:"max_deposit_period,omitempty" yaml:"max_deposit_period,omitempty"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months @@ -38,18 +51,27 @@ func NewDepositParams(minDeposit sdk.Coins, maxDepositPeriod time.Duration) Depo } } +// DefaultDepositParams default parameters for deposits +func DefaultDepositParams() DepositParams { + return NewDepositParams( + sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, DefaultMinDepositTokens)), + DefaultPeriod, + ) +} + +// String implements stringer insterface func (dp DepositParams) String() string { return fmt.Sprintf(`Deposit Params: Min Deposit: %s Max Deposit Period: %s`, dp.MinDeposit, dp.MaxDepositPeriod) } -// Checks equality of DepositParams +// Equal checks equality of DepositParams func (dp DepositParams) Equal(dp2 DepositParams) bool { return dp.MinDeposit.IsEqual(dp2.MinDeposit) && dp.MaxDepositPeriod == dp2.MaxDepositPeriod } -// Param around Tallying votes in governance +// TallyParams defines the params around Tallying votes in governance type TallyParams struct { Quorum sdk.Dec `json:"quorum,omitempty" yaml:"quorum,omitempty"` // Minimum percentage of total stake needed to vote for a result to be considered valid Threshold sdk.Dec `json:"threshold,omitempty" yaml:"threshold,omitempty"` // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5 @@ -65,6 +87,12 @@ func NewTallyParams(quorum, threshold, veto sdk.Dec) TallyParams { } } +// DefaultTallyParams default parameters for tallying +func DefaultTallyParams() TallyParams { + return NewTallyParams(DefaultQuorum, DefaultThreshold, DefaultVeto) +} + +// String implements stringer insterface func (tp TallyParams) String() string { return fmt.Sprintf(`Tally Params: Quorum: %s @@ -73,7 +101,7 @@ func (tp TallyParams) String() string { tp.Quorum, tp.Threshold, tp.Veto) } -// Param around Voting in governance +// VotingParams defines the params around Voting in governance type VotingParams struct { VotingPeriod time.Duration `json:"voting_period,omitempty" yaml:"voting_period,omitempty"` // Length of the voting period. } @@ -85,6 +113,12 @@ func NewVotingParams(votingPeriod time.Duration) VotingParams { } } +// DefaultVotingParams default parameters for voting +func DefaultVotingParams() VotingParams { + return NewVotingParams(DefaultPeriod) +} + +// String implements stringer interface func (vp VotingParams) String() string { return fmt.Sprintf(`Voting Params: Voting Period: %s`, vp.VotingPeriod) @@ -102,6 +136,7 @@ func (gp Params) String() string { gp.TallyParams.String() + "\n" + gp.DepositParams.String() } +// NewParams creates a new gov Params instance func NewParams(vp VotingParams, tp TallyParams, dp DepositParams) Params { return Params{ VotingParams: vp, @@ -109,3 +144,8 @@ func NewParams(vp VotingParams, tp TallyParams, dp DepositParams) Params { TallyParams: tp, } } + +// DefaultParams default governance params +func DefaultParams() Params { + return NewParams(DefaultVotingParams(), DefaultTallyParams(), DefaultDepositParams()) +} diff --git a/x/gov/types/proposal.go b/x/gov/types/proposal.go index a44b80730..0a1a54446 100644 --- a/x/gov/types/proposal.go +++ b/x/gov/types/proposal.go @@ -9,6 +9,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// DefaultStartingProposalID is 1 +const DefaultStartingProposalID uint64 = 1 + // Proposal defines a struct used by the governance module to allow for voting // on network changes. type Proposal struct { @@ -26,6 +29,7 @@ type Proposal struct { VotingEndTime time.Time `json:"voting_end_time" yaml:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied } +// NewProposal creates a new Proposal instance func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) Proposal { return Proposal{ Content: content, @@ -38,7 +42,7 @@ func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Tim } } -// nolint +// String implements stringer interface func (p Proposal) String() string { return fmt.Sprintf(`Proposal %d: Title: %s @@ -59,7 +63,7 @@ func (p Proposal) String() string { // Proposals is an array of proposal type Proposals []Proposal -// nolint +// String implements stringer interface func (p Proposals) String() string { out := "ID - (Status) [Type] Title\n" for _, prop := range p { @@ -71,14 +75,14 @@ func (p Proposals) String() string { } type ( - // ProposalQueue + // ProposalQueue defines a queue for proposal ids ProposalQueue []uint64 // ProposalStatus is a type alias that represents a proposal status as a byte ProposalStatus byte ) -//nolint +// Valid Proposal statuses const ( StatusNil ProposalStatus = 0x00 StatusDepositPeriod ProposalStatus = 0x01 @@ -88,7 +92,7 @@ const ( StatusFailed ProposalStatus = 0x05 ) -// ProposalStatusToString turns a string into a ProposalStatus +// ProposalStatusFromString turns a string into a ProposalStatus func ProposalStatusFromString(str string) (ProposalStatus, error) { switch str { case "DepositPeriod": @@ -138,12 +142,12 @@ func (status *ProposalStatus) Unmarshal(data []byte) error { return nil } -// Marshals to JSON using string +// MarshalJSON Marshals to JSON using string representation of the status func (status ProposalStatus) MarshalJSON() ([]byte, error) { return json.Marshal(status.String()) } -// Unmarshals from JSON assuming Bech32 encoding +// UnmarshalJSON Unmarshals from JSON assuming Bech32 encoding func (status *ProposalStatus) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) @@ -195,84 +199,43 @@ func (status ProposalStatus) Format(s fmt.State, verb rune) { } } -// Tally Results -type TallyResult struct { - Yes sdk.Int `json:"yes" yaml:"yes"` - Abstain sdk.Int `json:"abstain" yaml:"abstain"` - No sdk.Int `json:"no" yaml:"no"` - NoWithVeto sdk.Int `json:"no_with_veto" yaml:"no_with_veto"` -} - -func NewTallyResult(yes, abstain, no, noWithVeto sdk.Int) TallyResult { - return TallyResult{ - Yes: yes, - Abstain: abstain, - No: no, - NoWithVeto: noWithVeto, - } -} - -func NewTallyResultFromMap(results map[VoteOption]sdk.Dec) TallyResult { - return TallyResult{ - Yes: results[OptionYes].TruncateInt(), - Abstain: results[OptionAbstain].TruncateInt(), - No: results[OptionNo].TruncateInt(), - NoWithVeto: results[OptionNoWithVeto].TruncateInt(), - } -} - -// EmptyTallyResult returns an empty TallyResult. -func EmptyTallyResult() TallyResult { - return TallyResult{ - Yes: sdk.ZeroInt(), - Abstain: sdk.ZeroInt(), - No: sdk.ZeroInt(), - NoWithVeto: sdk.ZeroInt(), - } -} - -// Equals returns if two proposals are equal. -func (tr TallyResult) Equals(comp TallyResult) bool { - return tr.Yes.Equal(comp.Yes) && - tr.Abstain.Equal(comp.Abstain) && - tr.No.Equal(comp.No) && - tr.NoWithVeto.Equal(comp.NoWithVeto) -} - -func (tr TallyResult) String() string { - return fmt.Sprintf(`Tally Result: - Yes: %s - Abstain: %s - No: %s - NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto) -} - // Proposal types const ( ProposalTypeText string = "Text" ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade" ) -// Text Proposal +// TextProposal defines a standard text proposal whose changes need to be +// manually updated in case of approval type TextProposal struct { Title string `json:"title" yaml:"title"` Description string `json:"description" yaml:"description"` } +// NewTextProposal creates a text proposal Content func NewTextProposal(title, description string) Content { return TextProposal{title, description} } -// Implements Proposal Interface +// Implements Content Interface var _ Content = TextProposal{} -// nolint -func (tp TextProposal) GetTitle() string { return tp.Title } -func (tp TextProposal) GetDescription() string { return tp.Description } -func (tp TextProposal) ProposalRoute() string { return RouterKey } -func (tp TextProposal) ProposalType() string { return ProposalTypeText } +// GetTitle returns the proposal title +func (tp TextProposal) GetTitle() string { return tp.Title } + +// GetDescription returns the proposal description +func (tp TextProposal) GetDescription() string { return tp.Description } + +// ProposalRoute returns the proposal router key +func (tp TextProposal) ProposalRoute() string { return RouterKey } + +// ProposalType is "Text" +func (tp TextProposal) ProposalType() string { return ProposalTypeText } + +// ValidateBasic validates the content's title and description of the proposal func (tp TextProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, tp) } +// String implements Stringer interface func (tp TextProposal) String() string { return fmt.Sprintf(`Text Proposal: Title: %s @@ -280,7 +243,9 @@ func (tp TextProposal) String() string { `, tp.Title, tp.Description) } -// Software Upgrade Proposals +// SoftwareUpgradeProposal defines a proposal for upgrading the network nodes +// without the need of manually halting at a given height +// // TODO: We have to add fields for SUP specific arguments e.g. commit hash, // upgrade date, etc. type SoftwareUpgradeProposal struct { @@ -288,22 +253,32 @@ type SoftwareUpgradeProposal struct { Description string `json:"description" yaml:"description"` } +// NewSoftwareUpgradeProposal creates a software upgrade proposal Content func NewSoftwareUpgradeProposal(title, description string) Content { return SoftwareUpgradeProposal{title, description} } -// Implements Proposal Interface +// Implements Content Interface var _ Content = SoftwareUpgradeProposal{} -// nolint -func (sup SoftwareUpgradeProposal) GetTitle() string { return sup.Title } +// GetTitle returns the proposal title +func (sup SoftwareUpgradeProposal) GetTitle() string { return sup.Title } + +// GetDescription returns the proposal description func (sup SoftwareUpgradeProposal) GetDescription() string { return sup.Description } -func (sup SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey } -func (sup SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade } + +// ProposalRoute returns the proposal router key +func (sup SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey } + +// ProposalType is "SoftwareUpgrade" +func (sup SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade } + +// ValidateBasic validates the content's title and description of the proposal func (sup SoftwareUpgradeProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, sup) } +// String implements Stringer interface func (sup SoftwareUpgradeProposal) String() string { return fmt.Sprintf(`Software Upgrade Proposal: Title: %s diff --git a/x/gov/types/querier.go b/x/gov/types/querier.go index cfe0abc4d..2b14592ee 100644 --- a/x/gov/types/querier.go +++ b/x/gov/types/querier.go @@ -4,6 +4,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// DONTCOVER + // query endpoints supported by the governance Querier const ( QueryParams = "params" @@ -20,7 +22,7 @@ const ( ParamTallying = "tallying" ) -// Params for queries: +// QueryProposalParams Params for queries: // - 'custom/gov/proposal' // - 'custom/gov/deposits' // - 'custom/gov/tally' @@ -29,20 +31,20 @@ type QueryProposalParams struct { ProposalID uint64 } -// creates a new instance of QueryProposalParams +// NewQueryProposalParams creates a new instance of QueryProposalParams func NewQueryProposalParams(proposalID uint64) QueryProposalParams { return QueryProposalParams{ ProposalID: proposalID, } } -// Params for query 'custom/gov/deposit' +// QueryDepositParams params for query 'custom/gov/deposit' type QueryDepositParams struct { ProposalID uint64 Depositor sdk.AccAddress } -// creates a new instance of QueryDepositParams +// NewQueryDepositParams creates a new instance of QueryDepositParams func NewQueryDepositParams(proposalID uint64, depositor sdk.AccAddress) QueryDepositParams { return QueryDepositParams{ ProposalID: proposalID, @@ -50,13 +52,13 @@ func NewQueryDepositParams(proposalID uint64, depositor sdk.AccAddress) QueryDep } } -// Params for query 'custom/gov/vote' +// QueryVoteParams Params for query 'custom/gov/vote' type QueryVoteParams struct { ProposalID uint64 Voter sdk.AccAddress } -// creates a new instance of QueryVoteParams +// NewQueryVoteParams creates a new instance of QueryVoteParams func NewQueryVoteParams(proposalID uint64, voter sdk.AccAddress) QueryVoteParams { return QueryVoteParams{ ProposalID: proposalID, @@ -64,7 +66,7 @@ func NewQueryVoteParams(proposalID uint64, voter sdk.AccAddress) QueryVoteParams } } -// Params for query 'custom/gov/proposals' +// QueryProposalsParams Params for query 'custom/gov/proposals' type QueryProposalsParams struct { Voter sdk.AccAddress Depositor sdk.AccAddress @@ -72,7 +74,7 @@ type QueryProposalsParams struct { Limit uint64 } -// creates a new instance of QueryProposalsParams +// NewQueryProposalsParams creates a new instance of QueryProposalsParams func NewQueryProposalsParams(status ProposalStatus, limit uint64, voter, depositor sdk.AccAddress) QueryProposalsParams { return QueryProposalsParams{ Voter: voter, diff --git a/x/gov/router.go b/x/gov/types/router.go similarity index 96% rename from x/gov/router.go rename to x/gov/types/router.go index 7c86c6cf3..3901d8869 100644 --- a/x/gov/router.go +++ b/x/gov/types/router.go @@ -1,4 +1,4 @@ -package gov +package types import ( "fmt" @@ -26,6 +26,7 @@ type router struct { sealed bool } +// NewRouter creates a new Router interface instance func NewRouter() Router { return &router{ routes: make(map[string]Handler), diff --git a/x/gov/types/tally.go b/x/gov/types/tally.go new file mode 100644 index 000000000..63f20d4b7 --- /dev/null +++ b/x/gov/types/tally.go @@ -0,0 +1,78 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ValidatorGovInfo used for tallying +type ValidatorGovInfo struct { + Address sdk.ValAddress // address of the validator operator + BondedTokens sdk.Int // Power of a Validator + DelegatorShares sdk.Dec // Total outstanding delegator shares + DelegatorDeductions sdk.Dec // Delegator deductions from validator's delegators voting independently + Vote VoteOption // Vote of the validator +} + +// NewValidatorGovInfo creates a ValidatorGovInfo instance +func NewValidatorGovInfo(address sdk.ValAddress, bondedTokens sdk.Int, delegatorShares, + delegatorDeductions sdk.Dec, vote VoteOption) ValidatorGovInfo { + + return ValidatorGovInfo{ + Address: address, + BondedTokens: bondedTokens, + DelegatorShares: delegatorShares, + DelegatorDeductions: delegatorDeductions, + Vote: vote, + } +} + +// TallyResult defines a standard tally for a proposal +type TallyResult struct { + Yes sdk.Int `json:"yes" yaml:"yes"` + Abstain sdk.Int `json:"abstain" yaml:"abstain"` + No sdk.Int `json:"no" yaml:"no"` + NoWithVeto sdk.Int `json:"no_with_veto" yaml:"no_with_veto"` +} + +// NewTallyResult creates a new TallyResult instance +func NewTallyResult(yes, abstain, no, noWithVeto sdk.Int) TallyResult { + return TallyResult{ + Yes: yes, + Abstain: abstain, + No: no, + NoWithVeto: noWithVeto, + } +} + +// NewTallyResultFromMap creates a new TallyResult instance from a Option -> Dec map +func NewTallyResultFromMap(results map[VoteOption]sdk.Dec) TallyResult { + return NewTallyResult( + results[OptionYes].TruncateInt(), + results[OptionAbstain].TruncateInt(), + results[OptionNo].TruncateInt(), + results[OptionNoWithVeto].TruncateInt(), + ) +} + +// EmptyTallyResult returns an empty TallyResult. +func EmptyTallyResult() TallyResult { + return NewTallyResult(sdk.ZeroInt(), sdk.ZeroInt(), sdk.ZeroInt(), sdk.ZeroInt()) +} + +// Equals returns if two proposals are equal. +func (tr TallyResult) Equals(comp TallyResult) bool { + return tr.Yes.Equal(comp.Yes) && + tr.Abstain.Equal(comp.Abstain) && + tr.No.Equal(comp.No) && + tr.NoWithVeto.Equal(comp.NoWithVeto) +} +// String implements stringer interface +func (tr TallyResult) String() string { + return fmt.Sprintf(`Tally Result: + Yes: %s + Abstain: %s + No: %s + NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto) +} diff --git a/x/gov/vote.go b/x/gov/vote.go deleted file mode 100644 index 7ebb352cc..000000000 --- a/x/gov/vote.go +++ /dev/null @@ -1,83 +0,0 @@ -package gov - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/gov/types" -) - -// AddVote Adds a vote on a specific proposal -func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { - proposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - return ErrUnknownProposal(keeper.codespace, proposalID) - } - if proposal.Status != StatusVotingPeriod { - return ErrInactiveProposal(keeper.codespace, proposalID) - } - - if !ValidVoteOption(option) { - return ErrInvalidVote(keeper.codespace, option) - } - - vote := NewVote(proposalID, voterAddr, option) - keeper.setVote(ctx, proposalID, voterAddr, vote) - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeProposalVote, - sdk.NewAttribute(types.AttributeKeyOption, option.String()), - sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposalID)), - ), - ) - - return nil -} - -// GetAllVotes returns all the votes from the store -func (keeper Keeper) GetAllVotes(ctx sdk.Context) (votes Votes) { - keeper.IterateAllVotes(ctx, func(vote Vote) bool { - votes = append(votes, vote) - return false - }) - return -} - -// GetVotes returns all the votes from a proposal -func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) (votes Votes) { - keeper.IterateVotes(ctx, proposalID, func(vote Vote) bool { - votes = append(votes, vote) - return false - }) - return -} - -// GetVote gets the vote from an address on a specific proposal -func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (vote Vote, found bool) { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(types.VoteKey(proposalID, voterAddr)) - if bz == nil { - return vote, false - } - - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) - return vote, true -} - -func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) { - store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) - store.Set(types.VoteKey(proposalID, voterAddr), bz) -} - -// GetVotesIterator gets all the votes on a specific proposal as an sdk.Iterator -func (keeper Keeper) GetVotesIterator(ctx sdk.Context, proposalID uint64) sdk.Iterator { - store := ctx.KVStore(keeper.storeKey) - return sdk.KVStorePrefixIterator(store, types.VotesKey(proposalID)) -} - -func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { - store := ctx.KVStore(keeper.storeKey) - store.Delete(types.VoteKey(proposalID, voterAddr)) -} diff --git a/x/mint/internal/keeper/common_test.go b/x/mint/internal/keeper/common_test.go index 77b7985d3..57862d6bf 100644 --- a/x/mint/internal/keeper/common_test.go +++ b/x/mint/internal/keeper/common_test.go @@ -60,10 +60,10 @@ func newTestInput(t *testing.T) testInput { minterAcc := supply.NewEmptyModuleAccount(types.ModuleName, supply.Minter) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[feeCollectorAcc.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true - blacklistedAddrs[minterAcc.String()] = true + blacklistedAddrs[feeCollectorAcc.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true + blacklistedAddrs[minterAcc.GetAddress().String()] = true paramsKeeper := params.NewKeeper(types.ModuleCdc, keyParams, tkeyParams, params.DefaultCodespace) accountKeeper := auth.NewAccountKeeper(types.ModuleCdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) diff --git a/x/params/client/cli/tx.go b/x/params/client/cli/tx.go index 40aeaae83..79d6bb125 100644 --- a/x/params/client/cli/tx.go +++ b/x/params/client/cli/tx.go @@ -12,7 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - "github.com/cosmos/cosmos-sdk/x/gov" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" "github.com/cosmos/cosmos-sdk/x/params/types" ) @@ -75,7 +75,7 @@ Where proposal.json contains: from := cliCtx.GetFromAddress() content := types.NewParameterChangeProposal(proposal.Title, proposal.Description, proposal.Changes.ToParamChanges()) - msg := gov.NewMsgSubmitProposal(content, proposal.Deposit, from) + msg := govtypes.NewMsgSubmitProposal(content, proposal.Deposit, from) if err := msg.ValidateBasic(); err != nil { return err } diff --git a/x/params/client/rest/rest.go b/x/params/client/rest/rest.go index 4d95932b1..355296fdb 100644 --- a/x/params/client/rest/rest.go +++ b/x/params/client/rest/rest.go @@ -7,7 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - "github.com/cosmos/cosmos-sdk/x/gov" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" "github.com/cosmos/cosmos-sdk/x/params" paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" @@ -36,7 +36,7 @@ func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { content := params.NewParameterChangeProposal(req.Title, req.Description, req.Changes.ToParamChanges()) - msg := gov.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) + msg := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/params/proposal_handler.go b/x/params/proposal_handler.go index a40d8df83..97efd3e91 100644 --- a/x/params/proposal_handler.go +++ b/x/params/proposal_handler.go @@ -7,6 +7,7 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) +// NewParamChangeProposalHandler creates a new governance Handler for a ParamChangeProposal func NewParamChangeProposalHandler(k Keeper) govtypes.Handler { return func(ctx sdk.Context, content govtypes.Content) sdk.Error { switch c := content.(type) { diff --git a/x/params/simulation/msgs.go b/x/params/simulation/msgs.go index df303a8b2..db7669ae0 100644 --- a/x/params/simulation/msgs.go +++ b/x/params/simulation/msgs.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/gov" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/simulation" ) @@ -158,7 +158,7 @@ var paramChangePool = []simParamChange{ // SimulateParamChangeProposalContent returns random parameter change content. // It will generate a ParameterChangeProposal object with anywhere between 1 and // 3 parameter changes all of which have random, but valid values. -func SimulateParamChangeProposalContent(r *rand.Rand, _ *baseapp.BaseApp, _ sdk.Context, _ []simulation.Account) gov.Content { +func SimulateParamChangeProposalContent(r *rand.Rand, _ *baseapp.BaseApp, _ sdk.Context, _ []simulation.Account) govtypes.Content { numChanges := simulation.RandIntBetween(r, 1, len(paramChangePool)/2) paramChanges := make([]params.ParamChange, numChanges, numChanges) paramChangesKeys := make(map[string]struct{}) diff --git a/x/params/types/keys.go b/x/params/types/keys.go index e6b1e301c..1355a7427 100644 --- a/x/params/types/keys.go +++ b/x/params/types/keys.go @@ -1,7 +1,7 @@ package types const ( - // ModuleKey defines the name of the module + // ModuleName defines the name of the module ModuleName = "params" // RouterKey defines the routing key for a ParameterChangeProposal diff --git a/x/params/types/proposal.go b/x/params/types/proposal.go index 1039a082e..58d559615 100644 --- a/x/params/types/proposal.go +++ b/x/params/types/proposal.go @@ -39,7 +39,7 @@ func (pcp ParameterChangeProposal) GetTitle() string { return pcp.Title } // GetDescription returns the description of a parameter change proposal. func (pcp ParameterChangeProposal) GetDescription() string { return pcp.Description } -// GetDescription returns the routing key of a parameter change proposal. +// ProposalRoute returns the routing key of a parameter change proposal. func (pcp ParameterChangeProposal) ProposalRoute() string { return RouterKey } // ProposalType returns the type of a parameter change proposal. @@ -103,7 +103,7 @@ func (pc ParamChange) String() string { `, pc.Subspace, pc.Key, pc.Subkey, pc.Value) } -// ValidateChange performs basic validation checks over a set of ParamChange. It +// ValidateChanges performs basic validation checks over a set of ParamChange. It // returns an error if any ParamChange is invalid. func ValidateChanges(changes []ParamChange) sdk.Error { if len(changes) == 0 { diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index a1ce02fef..2d3373f67 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -43,9 +43,9 @@ func getMockApp(t *testing.T) (*mock.App, staking.Keeper, Keeper) { bondPool := supply.NewEmptyModuleAccount(types.BondedPoolName, supply.Burner, supply.Staking) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[feeCollector.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true + blacklistedAddrs[feeCollector.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) maccPerms := map[string][]string{ diff --git a/x/slashing/internal/keeper/test_common.go b/x/slashing/internal/keeper/test_common.go index 6d3251d6c..384b24eb7 100644 --- a/x/slashing/internal/keeper/test_common.go +++ b/x/slashing/internal/keeper/test_common.go @@ -86,9 +86,9 @@ func CreateTestInput(t *testing.T, defaults types.Params) (sdk.Context, bank.Kee bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[feeCollectorAcc.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true + blacklistedAddrs[feeCollectorAcc.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) diff --git a/x/staking/app_test.go b/x/staking/app_test.go index 1f148de06..8385f88f1 100644 --- a/x/staking/app_test.go +++ b/x/staking/app_test.go @@ -31,9 +31,9 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { bondPool := supply.NewEmptyModuleAccount(types.BondedPoolName, supply.Burner, supply.Staking) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[feeCollector.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true + blacklistedAddrs[feeCollector.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) maccPerms := map[string][]string{ diff --git a/x/staking/keeper/test_common.go b/x/staking/keeper/test_common.go index 86c9b83f2..cc178408c 100644 --- a/x/staking/keeper/test_common.go +++ b/x/staking/keeper/test_common.go @@ -112,9 +112,9 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context bondPool := supply.NewEmptyModuleAccount(types.BondedPoolName, supply.Burner, supply.Staking) blacklistedAddrs := make(map[string]bool) - blacklistedAddrs[feeCollectorAcc.String()] = true - blacklistedAddrs[notBondedPool.String()] = true - blacklistedAddrs[bondPool.String()] = true + blacklistedAddrs[feeCollectorAcc.GetAddress().String()] = true + blacklistedAddrs[notBondedPool.GetAddress().String()] = true + blacklistedAddrs[bondPool.GetAddress().String()] = true pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)