diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 4b143926dc..bfc733b265 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -279,3 +279,50 @@ func TestProposalPassedEndblocker(t *testing.T) { resTags := EndBlocker(ctx, input.keeper) require.Equal(t, sdk.MakeTag(tags.ProposalResult, tags.ActionProposalPassed), resTags[1]) } + +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) + + handler := NewHandler(input.keeper) + stakingHandler := staking.NewHandler(input.sk) + + header := abci.Header{Height: input.mApp.LastBlockHeight() + 1} + input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + ctx := input.mApp.BaseApp.NewContext(false, abci.Header{}) + + valAddr := sdk.ValAddress(input.addrs[0]) + + input.keeper.ck.SetSendEnabled(ctx, true) + createValidators(t, stakingHandler, ctx, []sdk.ValAddress{valAddr}, []int64{10}) + staking.EndBlocker(ctx, input.sk) + + // 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()) + require.NoError(t, err) + + proposalCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10))) + newDepositMsg := NewMsgDeposit(input.addrs[0], proposal.ProposalID, proposalCoins) + res := handler(ctx, newDepositMsg) + require.True(t, res.IsOK()) + + err = input.keeper.AddVote(ctx, proposal.ProposalID, input.addrs[0], OptionYes) + require.NoError(t, err) + + newHeader := ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(input.keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(input.keeper.GetVotingParams(ctx).VotingPeriod) + ctx = ctx.WithBlockHeader(newHeader) + + // Set the contextKeyBadProposal value to false so that the handler will fail + // during the processing of the proposal in the EndBlocker. + ctx = ctx.WithValue(contextKeyBadProposal, false) + + // validate that the proposal fails/has been rejected + resTags := EndBlocker(ctx, input.keeper) + require.Equal(t, sdk.MakeTag(tags.ProposalResult, tags.ActionProposalFailed), resTags[1]) +} diff --git a/x/gov/test_common.go b/x/gov/test_common.go index b9d857c0d6..7293112363 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -2,6 +2,7 @@ package gov import ( "bytes" + "fmt" "log" "sort" "testing" @@ -155,6 +156,28 @@ 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 { + switch c.ProposalType() { + case ProposalTypeText, ProposalTypeSoftwareUpgrade: + v := ctx.Value(contextKeyBadProposal) + + if v == nil || !v.(bool) { + return sdk.ErrInternal("proposal failed") + } + + return nil + + default: + errMsg := fmt.Sprintf("unrecognized gov proposal type: %s", c.ProposalType()) + return sdk.ErrUnknownRequest(errMsg) + } +} + // 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),