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:
parent
603e89541f
commit
bffcae54a1
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue