Add hooks to governance actions (#9133)

* 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 <ahmedaly113@outlook.com>
This commit is contained in:
Sunny Aggarwal 2021-04-21 12:59:30 -04:00 committed by GitHub
parent 603e89541f
commit bffcae54a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 240 additions and 1 deletions

View File

@ -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. * (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. * [\#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. * [#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 ### 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. * [\#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.

View File

@ -273,11 +273,17 @@ func NewSimApp(
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)). AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)).
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)) 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, appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
&stakingKeeper, govRouter, &stakingKeeper, govRouter,
) )
app.GovKeeper = *govKeeper.SetHooks(
govtypes.NewMultiGovHooks(
// register the governance hooks
),
)
// create evidence keeper with router // create evidence keeper with router
evidenceKeeper := evidencekeeper.NewKeeper( evidenceKeeper := evidencekeeper.NewKeeper(
appCodec, keys[evidencetypes.StoreKey], &app.StakingKeeper, app.SlashingKeeper, appCodec, keys[evidencetypes.StoreKey], &app.StakingKeeper, app.SlashingKeeper,

View File

@ -21,6 +21,9 @@ func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) {
keeper.DeleteProposal(ctx, proposal.ProposalId) keeper.DeleteProposal(ctx, proposal.ProposalId)
keeper.DeleteDeposits(ctx, proposal.ProposalId) keeper.DeleteDeposits(ctx, proposal.ProposalId)
// called when proposal become inactive
keeper.AfterProposalFailedMinDeposit(ctx, proposal.ProposalId)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeInactiveProposal, types.EventTypeInactiveProposal,
@ -89,6 +92,9 @@ func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) {
keeper.SetProposal(ctx, proposal) keeper.SetProposal(ctx, proposal)
keeper.RemoveFromActiveProposalQueue(ctx, proposal.ProposalId, proposal.VotingEndTime) keeper.RemoveFromActiveProposalQueue(ctx, proposal.ProposalId, proposal.VotingEndTime)
// when proposal become active
keeper.AfterProposalVotingPeriodEnded(ctx, proposal.ProposalId)
logger.Info( logger.Info(
"proposal tallied", "proposal tallied",
"proposal", proposal.ProposalId, "proposal", proposal.ProposalId,

View File

@ -150,6 +150,9 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd
deposit = types.NewDeposit(proposalID, depositorAddr, depositAmount) 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( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeProposalDeposit, types.EventTypeProposalDeposit,

44
x/gov/keeper/hooks.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -23,6 +23,9 @@ type Keeper struct {
// The reference to the DelegationSet and ValidatorSet to get information about validators and delegators // The reference to the DelegationSet and ValidatorSet to get information about validators and delegators
sk types.StakingKeeper sk types.StakingKeeper
// GovHooks
hooks types.GovHooks
// The (unexposed) keys used to access the stores from the Context. // The (unexposed) keys used to access the stores from the Context.
storeKey sdk.StoreKey storeKey sdk.StoreKey
@ -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. // Logger returns a module-specific logger.
func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { func (keeper Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+types.ModuleName) return ctx.Logger().With("module", "x/"+types.ModuleName)

View File

@ -41,6 +41,9 @@ func (keeper Keeper) SubmitProposal(ctx sdk.Context, content types.Content) (typ
keeper.InsertInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime) keeper.InsertInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime)
keeper.SetProposalID(ctx, proposalID+1) keeper.SetProposalID(ctx, proposalID+1)
// called right after a proposal is submitted
keeper.AfterProposalSubmission(ctx, proposalID)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeSubmitProposal, types.EventTypeSubmitProposal,

View File

@ -27,6 +27,9 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A
vote := types.NewVote(proposalID, voterAddr, options) vote := types.NewVote(proposalID, voterAddr, options)
keeper.SetVote(ctx, vote) keeper.SetVote(ctx, vote)
// called after a vote on a proposal is cast
keeper.AfterProposalVote(ctx, proposalID, voterAddr)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeProposalVote, types.EventTypeProposalVote,

View File

@ -48,3 +48,16 @@ type BankKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, name 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
x/gov/types/hooks.go Normal file
View File

@ -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)
}
}