-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
463fdc1
commit 78bf911
Showing
3 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/v7/modules/core/ante" | ||
ibckeeper "github.com/cosmos/ibc-go/v7/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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.GetParams(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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
tmrand "github.com/cometbft/cometbft/libs/rand" | ||
tmproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
"github.com/cosmos/cosmos-sdk/client" | ||
"github.com/cosmos/cosmos-sdk/testutil/testdata" | ||
|
||
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.MakeTestEncodingConfig() | ||
Check failure on line 42 in ante/gov_ante_test.go GitHub Actions / test
|
||
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) | ||
} | ||
} | ||
} |