Skip to content

Commit

Permalink
Add hooks to governance actions (cosmos#9133)
Browse files Browse the repository at this point in the history
* add governance hooks

* fix lint

* fix lint

* CHANGELOG

* sh -> gh

* improve comments

* add test

* add more tests

* rename two of the hooks

Co-authored-by: ahmedaly113 <[email protected]>
  • Loading branch information
sunnya97 and ahmedaly113 authored Apr 21, 2021
1 parent 603e895 commit bffcae5
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (rosetta) [\#8729](https://github.com/cosmos/cosmos-sdk/pull/8729) Data API fully supports balance tracking. Construction API can now construct any message supported by the application.
* [\#8754](https://github.com/cosmos/cosmos-sdk/pull/8875) Added support for reverse iteration to pagination.
* [#9088](https://github.com/cosmos/cosmos-sdk/pull/9088) Added implementation to ADR-28 Derived Addresses.
* [\#9133](https://github.com/cosmos/cosmos-sdk/pull/9133) Added hooks for governance actions.

### Client Breaking Changes
* [\#8363](https://github.com/cosmos/cosmos-sdk/pull/8363) Addresses no longer have a fixed 20-byte length. From the SDK modules' point of view, any 1-255 bytes-long byte array is a valid address.
Expand Down
8 changes: 7 additions & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,17 @@ func NewSimApp(
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)).
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper))
app.GovKeeper = govkeeper.NewKeeper(
govKeeper := govkeeper.NewKeeper(
appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
&stakingKeeper, govRouter,
)

app.GovKeeper = *govKeeper.SetHooks(
govtypes.NewMultiGovHooks(
// register the governance hooks
),
)

// create evidence keeper with router
evidenceKeeper := evidencekeeper.NewKeeper(
appCodec, keys[evidencetypes.StoreKey], &app.StakingKeeper, app.SlashingKeeper,
Expand Down
6 changes: 6 additions & 0 deletions x/gov/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) {
keeper.DeleteProposal(ctx, proposal.ProposalId)
keeper.DeleteDeposits(ctx, proposal.ProposalId)

// called when proposal become inactive
keeper.AfterProposalFailedMinDeposit(ctx, proposal.ProposalId)

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeInactiveProposal,
Expand Down Expand Up @@ -89,6 +92,9 @@ func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) {
keeper.SetProposal(ctx, proposal)
keeper.RemoveFromActiveProposalQueue(ctx, proposal.ProposalId, proposal.VotingEndTime)

// when proposal become active
keeper.AfterProposalVotingPeriodEnded(ctx, proposal.ProposalId)

logger.Info(
"proposal tallied",
"proposal", proposal.ProposalId,
Expand Down
3 changes: 3 additions & 0 deletions x/gov/keeper/deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd
deposit = types.NewDeposit(proposalID, depositorAddr, depositAmount)
}

// called when deposit has been added to a proposal, however the proposal may not be active
keeper.AfterProposalDeposit(ctx, proposalID, depositorAddr)

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeProposalDeposit,
Expand Down
44 changes: 44 additions & 0 deletions x/gov/keeper/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)

// Implements GovHooks interface
var _ types.GovHooks = Keeper{}

// AfterProposalSubmission - call hook if registered
func (keeper Keeper) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) {
if keeper.hooks != nil {
keeper.hooks.AfterProposalSubmission(ctx, proposalID)
}
}

// AfterProposalDeposit - call hook if registered
func (keeper Keeper) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) {
if keeper.hooks != nil {
keeper.hooks.AfterProposalDeposit(ctx, proposalID, depositorAddr)
}
}

// AfterProposalVote - call hook if registered
func (keeper Keeper) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) {
if keeper.hooks != nil {
keeper.hooks.AfterProposalVote(ctx, proposalID, voterAddr)
}
}

// AfterProposalFailedMinDeposit - call hook if registered
func (keeper Keeper) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) {
if keeper.hooks != nil {
keeper.hooks.AfterProposalFailedMinDeposit(ctx, proposalID)
}
}

// AfterProposalVotingPeriodEnded - call hook if registered
func (keeper Keeper) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) {
if keeper.hooks != nil {
keeper.hooks.AfterProposalVotingPeriodEnded(ctx, proposalID)
}
}
96 changes: 96 additions & 0 deletions x/gov/keeper/hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package keeper_test

import (
"testing"
"time"

"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/gov/keeper"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)

var _ types.GovHooks = &MockGovHooksReceiver{}

// GovHooks event hooks for governance proposal object (noalias)
type MockGovHooksReceiver struct {
AfterProposalSubmissionValid bool
AfterProposalDepositValid bool
AfterProposalVoteValid bool
AfterProposalFailedMinDepositValid bool
AfterProposalVotingPeriodEndedValid bool
}

func (h *MockGovHooksReceiver) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) {
h.AfterProposalSubmissionValid = true
}

func (h *MockGovHooksReceiver) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) {
h.AfterProposalDepositValid = true
}

func (h *MockGovHooksReceiver) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) {
h.AfterProposalVoteValid = true
}
func (h *MockGovHooksReceiver) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) {
h.AfterProposalFailedMinDepositValid = true
}
func (h *MockGovHooksReceiver) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) {
h.AfterProposalVotingPeriodEndedValid = true
}

func TestHooks(t *testing.T) {
app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})

minDeposit := app.GovKeeper.GetDepositParams(ctx).MinDeposit
addrs := simapp.AddTestAddrs(app, ctx, 1, minDeposit[0].Amount)

govHooksReceiver := MockGovHooksReceiver{}

app.GovKeeper = *keeper.UpdateHooks(&app.GovKeeper,
types.NewMultiGovHooks(
&govHooksReceiver,
),
)

require.False(t, govHooksReceiver.AfterProposalSubmissionValid)
require.False(t, govHooksReceiver.AfterProposalDepositValid)
require.False(t, govHooksReceiver.AfterProposalVoteValid)
require.False(t, govHooksReceiver.AfterProposalFailedMinDepositValid)
require.False(t, govHooksReceiver.AfterProposalVotingPeriodEndedValid)

tp := TestProposal
_, err := app.GovKeeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
require.True(t, govHooksReceiver.AfterProposalSubmissionValid)

newHeader := ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(app.GovKeeper.GetDepositParams(ctx).MaxDepositPeriod).Add(time.Duration(1) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
gov.EndBlocker(ctx, app.GovKeeper)

require.True(t, govHooksReceiver.AfterProposalFailedMinDepositValid)

p2, err := app.GovKeeper.SubmitProposal(ctx, tp)
require.NoError(t, err)

activated, err := app.GovKeeper.AddDeposit(ctx, p2.ProposalId, addrs[0], minDeposit)
require.True(t, activated)
require.NoError(t, err)
require.True(t, govHooksReceiver.AfterProposalDepositValid)

err = app.GovKeeper.AddVote(ctx, p2.ProposalId, addrs[0], types.NewNonSplitVoteOption(types.OptionYes))
require.NoError(t, err)
require.True(t, govHooksReceiver.AfterProposalVoteValid)

newHeader = ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(app.GovKeeper.GetVotingParams(ctx).VotingPeriod).Add(time.Duration(1) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
gov.EndBlocker(ctx, app.GovKeeper)
require.True(t, govHooksReceiver.AfterProposalVotingPeriodEndedValid)
}
8 changes: 8 additions & 0 deletions x/gov/keeper/internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package keeper

import "github.com/cosmos/cosmos-sdk/x/gov/types"

func UpdateHooks(k *Keeper, h types.GovHooks) *Keeper {
k.hooks = h
return k
}
14 changes: 14 additions & 0 deletions x/gov/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type Keeper struct {
// The reference to the DelegationSet and ValidatorSet to get information about validators and delegators
sk types.StakingKeeper

// GovHooks
hooks types.GovHooks

// The (unexposed) keys used to access the stores from the Context.
storeKey sdk.StoreKey

Expand Down Expand Up @@ -66,6 +69,17 @@ func NewKeeper(
}
}

// SetHooks sets the hooks for governance
func (keeper *Keeper) SetHooks(gh types.GovHooks) *Keeper {
if keeper.hooks != nil {
panic("cannot set governance hooks twice")
}

keeper.hooks = gh

return keeper
}

// Logger returns a module-specific logger.
func (keeper Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+types.ModuleName)
Expand Down
3 changes: 3 additions & 0 deletions x/gov/keeper/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func (keeper Keeper) SubmitProposal(ctx sdk.Context, content types.Content) (typ
keeper.InsertInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime)
keeper.SetProposalID(ctx, proposalID+1)

// called right after a proposal is submitted
keeper.AfterProposalSubmission(ctx, proposalID)

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSubmitProposal,
Expand Down
3 changes: 3 additions & 0 deletions x/gov/keeper/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A
vote := types.NewVote(proposalID, voterAddr, options)
keeper.SetVote(ctx, vote)

// called after a vote on a proposal is cast
keeper.AfterProposalVote(ctx, proposalID, voterAddr)

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeProposalVote,
Expand Down
13 changes: 13 additions & 0 deletions x/gov/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ type BankKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error
}

// Event Hooks
// These can be utilized to communicate between a governance keeper and another
// keepers.

// GovHooks event hooks for governance proposal object (noalias)
type GovHooks interface {
AfterProposalSubmission(ctx sdk.Context, proposalID uint64) // Must be called after proposal is submitted
AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) // Must be called after a deposit is made
AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) // Must be called after a vote on a proposal is cast
AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) // Must be called when proposal fails to reach min deposit
AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) // Must be called when proposal's finishes it's voting period
}
42 changes: 42 additions & 0 deletions x/gov/types/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

var _ GovHooks = MultiGovHooks{}

// combine multiple governance hooks, all hook functions are run in array sequence
type MultiGovHooks []GovHooks

func NewMultiGovHooks(hooks ...GovHooks) MultiGovHooks {
return hooks
}

func (h MultiGovHooks) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) {
for i := range h {
h[i].AfterProposalSubmission(ctx, proposalID)
}
}

func (h MultiGovHooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) {
for i := range h {
h[i].AfterProposalDeposit(ctx, proposalID, depositorAddr)
}
}

func (h MultiGovHooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) {
for i := range h {
h[i].AfterProposalVote(ctx, proposalID, voterAddr)
}
}
func (h MultiGovHooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) {
for i := range h {
h[i].AfterProposalFailedMinDeposit(ctx, proposalID)
}
}
func (h MultiGovHooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) {
for i := range h {
h[i].AfterProposalVotingPeriodEnded(ctx, proposalID)
}
}

0 comments on commit bffcae5

Please sign in to comment.