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.
|
||||
* [\#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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
sk types.StakingKeeper
|
||||
|
||||
// GovHooks
|
||||
hooks types.GovHooks
|
||||
|
||||
// The (unexposed) keys used to access the stores from the Context.
|
||||
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.
|
||||
func (keeper Keeper) Logger(ctx sdk.Context) log.Logger {
|
||||
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.SetProposalID(ctx, proposalID+1)
|
||||
|
||||
// called right after a proposal is submitted
|
||||
keeper.AfterProposalSubmission(ctx, proposalID)
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeSubmitProposal,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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