Skip to content

Commit

Permalink
add globalfee ante
Browse files Browse the repository at this point in the history
  • Loading branch information
expertdicer committed Oct 31, 2023
1 parent fe87a6e commit 8eed669
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 0 deletions.
85 changes: 85 additions & 0 deletions ante/ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package ante

import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
ibcante "github.com/cosmos/ibc-go/v6/modules/core/ante"
ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper"

migaloofeeante "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/ante"
)

// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC
// channel keeper.
type HandlerOptions struct {
ante.HandlerOptions
Codec codec.BinaryCodec
GovKeeper *govkeeper.Keeper
IBCkeeper *ibckeeper.Keeper
ExtensionOptionChecker authante.ExtensionOptionChecker
BypassMinFeeMsgTypes []string
GlobalFeeSubspace paramtypes.Subspace
StakingSubspace paramtypes.Subspace
TxFeeChecker authante.TxFeeChecker
}

func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
if opts.AccountKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler")
}
if opts.BankKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler")
}
if opts.SignModeHandler == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for AnteHandler")
}
if opts.IBCkeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "IBC keeper is required for AnteHandler")
}
if opts.GlobalFeeSubspace.Name() == "" {
return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "globalfee param store is required for AnteHandler")
}
if opts.StakingSubspace.Name() == "" {
return nil, sdkerrors.Wrap(sdkerrors.ErrNotFound, "staking param store is required for AnteHandler")
}
if opts.GovKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "gov keeper is required for AnteHandler")
}

sigGasConsumer := opts.SigGasConsumer
if sigGasConsumer == nil {
sigGasConsumer = ante.DefaultSigVerificationGasConsumer
}

// maxBypassMinFeeMsgGasUsage is the maximum gas usage per message
// so that a transaction that contains only message types that can
// bypass the minimum fee can be accepted with a zero fee.
// For details, see gaiafeeante.NewFeeDecorator()
var maxBypassMinFeeMsgGasUsage uint64 = 200_000

anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
ante.NewExtensionOptionsDecorator(opts.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(opts.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper),
NewGovPreventSpamDecorator(opts.Codec, opts.GovKeeper),
migaloofeeante.NewFeeDecorator(opts.BypassMinFeeMsgTypes, opts.GlobalFeeSubspace, opts.StakingSubspace, maxBypassMinFeeMsgGasUsage),

ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper, opts.TxFeeChecker),
ante.NewSetPubKeyDecorator(opts.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(opts.AccountKeeper),
ante.NewSigGasConsumeDecorator(opts.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(opts.AccountKeeper, opts.SignModeHandler),
ante.NewIncrementSequenceDecorator(opts.AccountKeeper),
ibcante.NewRedundantRelayDecorator(opts.IBCkeeper),
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
}
96 changes: 96 additions & 0 deletions ante/gov_ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ante

import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/authz"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
)

// initial deposit must be greater than or equal to 10% of the minimum deposit
var minInitialDepositFraction = sdk.NewDecWithPrec(10, 2)

type GovPreventSpamDecorator struct {
govKeeper *govkeeper.Keeper
cdc codec.BinaryCodec
}

func NewGovPreventSpamDecorator(cdc codec.BinaryCodec, govKeeper *govkeeper.Keeper) GovPreventSpamDecorator {
return GovPreventSpamDecorator{
govKeeper: govKeeper,
cdc: cdc,
}
}

func (g GovPreventSpamDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx,
simulate bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// run checks only on CheckTx or simulate
if !ctx.IsCheckTx() || simulate {
return next(ctx, tx, simulate)
}

msgs := tx.GetMsgs()
if err = g.ValidateGovMsgs(ctx, msgs); err != nil {
return ctx, err
}

return next(ctx, tx, simulate)
}

// validateGovMsgs checks if the InitialDeposit amounts are greater than the minimum initial deposit amount
func (g GovPreventSpamDecorator) ValidateGovMsgs(ctx sdk.Context, msgs []sdk.Msg) error {
validMsg := func(m sdk.Msg) error {
if msg, ok := m.(*govv1.MsgSubmitProposal); ok {
// prevent messages with insufficient initial deposit amount
depositParams := g.govKeeper.GetDepositParams(ctx)
minInitialDeposit := g.calcMinInitialDeposit(depositParams.MinDeposit)
initialDeposit := sdk.NewCoins(msg.InitialDeposit...)
if initialDeposit.IsAllLT(minInitialDeposit) {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient initial deposit amount - required: %v", minInitialDeposit)
}
}

return nil
}

validAuthz := func(execMsg *authz.MsgExec) error {
for _, v := range execMsg.Msgs {
var innerMsg sdk.Msg
if err := g.cdc.UnpackAny(v, &innerMsg); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "cannot unmarshal authz exec msgs")
}
if err := validMsg(innerMsg); err != nil {
return err
}
}

return nil
}

for _, m := range msgs {
if msg, ok := m.(*authz.MsgExec); ok {
if err := validAuthz(msg); err != nil {
return err
}
continue
}

// validate normal msgs
if err := validMsg(m); err != nil {
return err
}
}
return nil
}

func (g GovPreventSpamDecorator) calcMinInitialDeposit(minDeposit sdk.Coins) (minInitialDeposit sdk.Coins) {
for _, coin := range minDeposit {
minInitialCoins := minInitialDepositFraction.MulInt(coin.Amount).RoundInt()
minInitialDeposit = minInitialDeposit.Add(sdk.NewCoin(coin.Denom, minInitialCoins))
}
return
}
92 changes: 92 additions & 0 deletions ante/gov_ante_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package ante_test

import (
"fmt"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
"github.com/stretchr/testify/suite"

"github.com/White-Whale-Defi-Platform/migaloo-chain/v3/ante"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

migalooapp "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/app"
)

var (
insufficientCoins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100))
minCoins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000000))
moreThanMinCoins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 2500000))
testAddr = sdk.AccAddress("test1")
)

type GovAnteHandlerTestSuite struct {
suite.Suite

app *migalooapp.MigalooApp
ctx sdk.Context
clientCtx client.Context
}

func (s *GovAnteHandlerTestSuite) SetupTest() {
app := migalooapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{
ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)),
Height: 1,
})

encodingConfig := migalooapp.MakeEncodingConfig()
encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry)

s.app = app
s.ctx = ctx
s.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
}

func TestGovSpamPreventionSuite(t *testing.T) {
suite.Run(t, new(GovAnteHandlerTestSuite))
}

func (s *GovAnteHandlerTestSuite) TestGlobalFeeMinimumGasFeeAnteHandler() {
// setup test
s.SetupTest()
tests := []struct {
title, description string
proposalType string
proposerAddr sdk.AccAddress
initialDeposit sdk.Coins
expectPass bool
}{
{"Passing proposal 1", "the purpose of this proposal is to pass", govv1beta1.ProposalTypeText, testAddr, minCoins, true},
{"Passing proposal 2", "the purpose of this proposal is to pass with more coins than minimum", govv1beta1.ProposalTypeText, testAddr, moreThanMinCoins, true},
{"Failing proposal", "the purpose of this proposal is to fail", govv1beta1.ProposalTypeText, testAddr, insufficientCoins, false},
}

decorator := ante.NewGovPreventSpamDecorator(s.app.AppCodec(), &s.app.GovKeeper)

for _, tc := range tests {
content, ok := govv1beta1.ContentFromProposalType(tc.title, tc.description, tc.proposalType)
s.Require().True(ok)
s.Require().NotNil(content)

msg, err := govv1beta1.NewMsgSubmitProposal(
content,
tc.initialDeposit,
tc.proposerAddr,
)

s.Require().NoError(err)

err = decorator.ValidateGovMsgs(s.ctx, []sdk.Msg{msg})
if tc.expectPass {
s.Require().NoError(err, "expected %v to pass", tc.title)
} else {
s.Require().Error(err, "expected %v to fail", tc.title)
}
}
}

0 comments on commit 8eed669

Please sign in to comment.