diff --git a/ante/ante.go b/ante/ante.go new file mode 100644 index 00000000..e8e44074 --- /dev/null +++ b/ante/ante.go @@ -0,0 +1,91 @@ +package ante + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "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" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types" + + 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 + WasmConfig *wasmTypes.WasmConfig + BypassMinFeeMsgTypes []string + GlobalFeeSubspace paramtypes.Subspace + StakingSubspace paramtypes.Subspace + TXCounterStoreKey storetypes.StoreKey +} + +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 + wasmkeeper.NewLimitSimulationGasDecorator(opts.WasmConfig.SimulationGasLimit), // after setup context to enforce limits early + wasmkeeper.NewCountTXDecorator(opts.TXCounterStoreKey), + 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 +} diff --git a/ante/gov_ante.go b/ante/gov_ante.go new file mode 100644 index 00000000..ace2944e --- /dev/null +++ b/ante/gov_ante.go @@ -0,0 +1,106 @@ +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" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" +) + +// 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) + } + } + if msg, ok := m.(*govv1beta1.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 +} diff --git a/ante/gov_ante_test.go b/ante/gov_ante_test.go new file mode 100644 index 00000000..bb71bc8a --- /dev/null +++ b/ante/gov_ante_test.go @@ -0,0 +1,91 @@ +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.SetupMigalooAppWithValSet(s.T()) + 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) + } + } +} diff --git a/app/app.go b/app/app.go index 8b70fba9..0a24f7a4 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,7 @@ import ( "sort" "strings" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" @@ -94,6 +95,7 @@ import ( ibcclient "github.com/cosmos/ibc-go/v6/modules/core/02-client" ibcclientclient "github.com/cosmos/ibc-go/v6/modules/core/02-client/client" ibcclienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + ibcchanneltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" ibchost "github.com/cosmos/ibc-go/v6/modules/core/24-host" ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper" @@ -139,6 +141,8 @@ import ( wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" appparams "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/app/params" + migalooante "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/ante" + // unnamed import of statik for swagger UI support _ "github.com/cosmos/cosmos-sdk/client/docs/statik" @@ -235,6 +239,7 @@ var ( intertx.AppModuleBasic{}, ibcfee.AppModuleBasic{}, ibchooks.AppModuleBasic{}, + globalfee.AppModule{}, ) // module account permissions @@ -748,6 +753,7 @@ func NewMigalooApp( tokenfactory.NewAppModule(app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper), router.NewAppModule(&app.RouterKeeper), ibchooks.NewAppModule(app.AccountKeeper), + globalfee.NewAppModule(app.GetSubspace(globalfee.ModuleName)), crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants), // always be last to make sure that it checks for all invariants and not only part of them ) @@ -783,6 +789,7 @@ func NewMigalooApp( wasm.ModuleName, tokenfactorytypes.ModuleName, alliancemoduletypes.ModuleName, + globalfee.ModuleName, ) app.mm.SetOrderEndBlockers( @@ -813,6 +820,7 @@ func NewMigalooApp( wasm.ModuleName, tokenfactorytypes.ModuleName, alliancemoduletypes.ModuleName, + globalfee.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -852,6 +860,7 @@ func NewMigalooApp( wasm.ModuleName, routertypes.ModuleName, alliancemoduletypes.ModuleName, + globalfee.ModuleName, ) // Uncomment if you want to set a custom migration order here. @@ -892,11 +901,25 @@ func NewMigalooApp( app.MountKVStores(keys) app.MountTransientStores(tkeys) app.MountMemoryStores(memKeys) + + var bypassMinFeeMsgTypes []string + bypassMinFeeMsgTypesOptions := appOpts.Get(appparams.BypassMinFeeMsgTypesKey) + if bypassMinFeeMsgTypesOptions == nil { + bypassMinFeeMsgTypes = GetDefaultBypassFeeMessages() + } else { + bypassMinFeeMsgTypes = cast.ToStringSlice(bypassMinFeeMsgTypesOptions) + } + + if err := app.ValidateBypassFeeMsgTypes(bypassMinFeeMsgTypes); err != nil { + app.Logger().Error("invalid 'bypass-min-fee-msg-types' config option", "error", err) + panic(fmt.Sprintf("invalid 'bypass-min-fee-msg-types' config option: %s", err)) + } + // register upgrade app.setupUpgradeHandlers(cfg) - anteHandler, err := NewAnteHandler( - HandlerOptions{ + anteHandler, err := migalooante.NewAnteHandler( + migalooante.HandlerOptions{ HandlerOptions: ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, @@ -904,9 +927,14 @@ func NewMigalooApp( SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), SigGasConsumer: ante.DefaultSigVerificationGasConsumer, }, - IBCKeeper: app.IBCKeeper, - WasmConfig: &wasmConfig, - TXCounterStoreKey: keys[wasm.StoreKey], + Codec: appCodec, + GovKeeper: &app.GovKeeper, + IBCKeeper: app.IBCKeeper, + WasmConfig: &wasmConfig, + BypassMinFeeMsgTypes: bypassMinFeeMsgTypes, + GlobalFeeSubspace: app.GetSubspace(globalfee.ModuleName), + StakingSubspace: app.GetSubspace(stakingtypes.ModuleName), + TXCounterStoreKey: keys[wasm.StoreKey], }, ) if err != nil { @@ -953,6 +981,25 @@ func NewMigalooApp( return app } +func GetDefaultBypassFeeMessages() []string { + return []string{ + sdk.MsgTypeURL(&ibcchanneltypes.MsgRecvPacket{}), + sdk.MsgTypeURL(&ibcchanneltypes.MsgAcknowledgement{}), + sdk.MsgTypeURL(&ibcclienttypes.MsgUpdateClient{}), + } +} + +// ValidateBypassFeeMsgTypes checks that a proto message type exists for all MsgTypes in bypassMinFeeMsgTypes +// An error is returned for the first msgType that cannot be resolved +func (app *MigalooApp) ValidateBypassFeeMsgTypes(bypassMinFeeMsgTypes []string) error { + for _, msgType := range bypassMinFeeMsgTypes { + if _, err := app.interfaceRegistry.Resolve(msgType); err != nil { + return err + } + } + return nil +} + // Name returns the name of the App func (app *MigalooApp) Name() string { return app.BaseApp.Name() } @@ -1099,6 +1146,7 @@ func (app *MigalooApp) setupUpgradeHandlers(cfg module.Configurator) { } for _, upgrade := range Upgrades { + upgrade := upgrade if upgradeInfo.Name == upgrade.UpgradeName { app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &upgrade.StoreUpgrades)) } @@ -1150,6 +1198,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(wasm.ModuleName) paramsKeeper.Subspace(routertypes.ModuleName) paramsKeeper.Subspace(alliancemoduletypes.ModuleName) + paramsKeeper.Subspace(globalfee.ModuleName) return paramsKeeper } diff --git a/app/params/globalfee.go b/app/params/globalfee.go new file mode 100644 index 00000000..838258d5 --- /dev/null +++ b/app/params/globalfee.go @@ -0,0 +1,55 @@ +package params + +import ( + "strings" + + serverconfig "github.com/cosmos/cosmos-sdk/server/config" +) + +var ( + // BypassMinFeeMsgTypesKey defines the configuration key for the + // BypassMinFeeMsgTypes value. + //nolint: gosec + BypassMinFeeMsgTypesKey = "bypass-min-fee-msg-types" + + // customMigalooConfigTemplate defines Migaloo's custom application configuration TOML template. + customMigalooConfigTemplate = ` +############################################################################### +### Custom Migaloo Configuration ### +############################################################################### +# bypass-min-fee-msg-types defines custom message types the operator may set that +# will bypass minimum fee checks during CheckTx. +# NOTE: +# bypass-min-fee-msg-types = [] will deactivate the bypass - no messages will be allowed to bypass the minimum fee check +# bypass-min-fee-msg-types = [...] will allow messages of specified type to bypass the minimum fee check +# removing bypass-min-fee-msg-types from the config file will apply the default values: +# ["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement", "/ibc.core.client.v1.MsgUpdateClient"] +# +# Example: +# bypass-min-fee-msg-types = ["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement", "/ibc.core.client.v1.MsgUpdateClient"] +bypass-min-fee-msg-types = [{{ range .BypassMinFeeMsgTypes }}{{ printf "%q, " . }}{{end}}] +` +) + +// CustomConfigTemplate defines Migaloo's custom application configuration TOML +// template. It extends the core SDK template. +func CustomConfigTemplate() string { + config := serverconfig.DefaultConfigTemplate + lines := strings.Split(config, "\n") + // add the Migaloo config at the second line of the file + lines[2] += customMigalooConfigTemplate + return strings.Join(lines, "\n") +} + +// CustomAppConfig defines Migaloo's custom application configuration. +type CustomAppConfig struct { + serverconfig.Config + + // BypassMinFeeMsgTypes defines custom message types the operator may set that + // will bypass minimum fee checks during CheckTx. + // NOTE: + // bypass-min-fee-msg-types = [] will deactivate the bypass - no messages will be allowed to bypass the minimum fee check + // bypass-min-fee-msg-types = [] will allow messages of specified type to bypass the minimum fee check + // omitting bypass-min-fee-msg-types from the config file will use the default values: ["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement", "/ibc.core.client.v1.MsgUpdateClient"] + BypassMinFeeMsgTypes []string `mapstructure:"bypass-min-fee-msg-types"` +} diff --git a/go.mod b/go.mod index 78ab887e..43f0f164 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,10 @@ require ( github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v6 v6.1.1-0.20230928173630-232c0f1cea39 github.com/cosmos/ibc-go/v6 v6.2.0 github.com/cosmos/interchain-accounts v0.4.3 + github.com/gogo/protobuf v1.3.3 + github.com/golang/protobuf v1.5.3 github.com/gorilla/mux v1.8.0 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/prometheus/client_golang v1.16.0 github.com/rakyll/statik v0.1.7 github.com/spf13/cast v1.5.1 @@ -20,6 +23,8 @@ require ( github.com/tendermint/tm-db v0.6.8-0.20221109095132-774cdfe7e6b0 github.com/terra-money/alliance v0.1.2 github.com/terra-money/core/v2 v2.4.1 + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 + google.golang.org/grpc v1.55.0 ) require ( @@ -75,10 +80,8 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect - github.com/gogo/protobuf v1.3.3 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v1.12.1 // indirect @@ -92,7 +95,6 @@ require ( github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect @@ -159,8 +161,6 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/x/globalfee/README.md b/x/globalfee/README.md new file mode 100644 index 00000000..746d5c14 --- /dev/null +++ b/x/globalfee/README.md @@ -0,0 +1,5 @@ +# Global fee module + +The Global fee module was supplied by the great folks at [TGrade](https://github.com/confio/tgrade) 👋, with minor modifications. All credits and big thanks go to the original authors. + +More information about Cosmoshub fee system please check [here](../../docs/modules/globalfee.md). diff --git a/x/globalfee/alias.go b/x/globalfee/alias.go new file mode 100644 index 00000000..4cccabe1 --- /dev/null +++ b/x/globalfee/alias.go @@ -0,0 +1,9 @@ +package globalfee + +import ( + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +const ( + ModuleName = types.ModuleName +) diff --git a/x/globalfee/ante/antetest/fee_test.go b/x/globalfee/ante/antetest/fee_test.go new file mode 100644 index 00000000..7cb14081 --- /dev/null +++ b/x/globalfee/ante/antetest/fee_test.go @@ -0,0 +1,599 @@ +package antetest + +import ( + "testing" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibcclienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + ibcchanneltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + "github.com/stretchr/testify/suite" + + migalooapp "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/app" + gaiafeeante "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/ante" + globfeetypes "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +func (s *IntegrationTestSuite) TestGetDefaultGlobalFees() { + // set globalfees and min gas price + globalfeeSubspace := s.SetupTestGlobalFeeStoreAndMinGasPrice([]sdk.DecCoin{}, &globfeetypes.Params{}) + + // set staking params + stakingParam := stakingtypes.DefaultParams() + bondDenom := "uatom" + stakingParam.BondDenom = bondDenom + stakingSubspace := s.SetupTestStakingSubspace(stakingParam) + + // setup antehandler + mfd := gaiafeeante.NewFeeDecorator(migalooapp.GetDefaultBypassFeeMessages(), globalfeeSubspace, stakingSubspace, newTestGasLimit()) + + defaultGlobalFees, err := mfd.DefaultZeroGlobalFee(s.ctx) + s.Require().NoError(err) + s.Require().Greater(len(defaultGlobalFees), 0) + + if defaultGlobalFees[0].Denom != bondDenom { + s.T().Fatalf("bond denom: %s, default global fee denom: %s", bondDenom, defaultGlobalFees[0].Denom) + } +} + +// test global fees and min_gas_price with bypass msg types. +// please note even globalfee=0, min_gas_price=0, we do not let fee=0random_denom pass +// paid fees are already sanitized by removing zero coins(through feeFlag parsing), so use sdk.NewCoins() to create it. +func (s *IntegrationTestSuite) TestGlobalFeeMinimumGasFeeAnteHandler() { + // setup test + s.SetupTest() + s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder() + priv1, _, addr1 := testdata.KeyTestPubAddr() + privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0} + + denominator := int64(100000) + high := sdk.NewDec(400).Quo(sdk.NewDec(denominator)) // 0.004 + med := sdk.NewDec(200).Quo(sdk.NewDec(denominator)) // 0.002 + low := sdk.NewDec(100).Quo(sdk.NewDec(denominator)) // 0.001 + + highFeeAmt := sdk.NewInt(high.MulInt64(int64(2) * denominator).RoundInt64()) + medFeeAmt := sdk.NewInt(med.MulInt64(int64(2) * denominator).RoundInt64()) + lowFeeAmt := sdk.NewInt(low.MulInt64(int64(2) * denominator).RoundInt64()) + + globalfeeParamsEmpty := &globfeetypes.Params{MinimumGasPrices: []sdk.DecCoin{}} + minGasPriceEmpty := []sdk.DecCoin{} + globalfeeParams0 := &globfeetypes.Params{MinimumGasPrices: []sdk.DecCoin{ + sdk.NewDecCoinFromDec("photon", sdk.NewDec(0)), + sdk.NewDecCoinFromDec("uatom", sdk.NewDec(0)), + }} + globalfeeParamsContain0 := &globfeetypes.Params{MinimumGasPrices: []sdk.DecCoin{ + sdk.NewDecCoinFromDec("photon", med), + sdk.NewDecCoinFromDec("uatom", sdk.NewDec(0)), + }} + minGasPrice0 := []sdk.DecCoin{ + sdk.NewDecCoinFromDec("stake", sdk.NewDec(0)), + sdk.NewDecCoinFromDec("uatom", sdk.NewDec(0)), + } + globalfeeParamsHigh := &globfeetypes.Params{ + MinimumGasPrices: []sdk.DecCoin{ + sdk.NewDecCoinFromDec("uatom", high), + }, + } + minGasPrice := []sdk.DecCoin{ + sdk.NewDecCoinFromDec("uatom", med), + sdk.NewDecCoinFromDec("stake", med), + } + globalfeeParamsLow := &globfeetypes.Params{ + MinimumGasPrices: []sdk.DecCoin{ + sdk.NewDecCoinFromDec("uatom", low), + }, + } + // global fee must be sorted in denom + globalfeeParamsNewDenom := &globfeetypes.Params{ + MinimumGasPrices: []sdk.DecCoin{ + sdk.NewDecCoinFromDec("photon", high), + sdk.NewDecCoinFromDec("quark", high), + }, + } + testCases := map[string]struct { + minGasPrice []sdk.DecCoin + globalFeeParams *globfeetypes.Params + gasPrice sdk.Coins + gasLimit sdk.Gas + txMsg sdk.Msg + txCheck bool + expErr bool + }{ + // test fees + // empty min_gas_price or empty global fee + "empty min_gas_price, nonempty global fee, fee higher/equal than global_fee": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParamsHigh, + // sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()) + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", highFeeAmt)), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "empty min_gas_price, nonempty global fee, fee lower than global_fee": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParamsHigh, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "nonempty min_gas_price with defaultGlobalFee denom, empty global fee, fee higher/equal than min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsEmpty, // default 0uatom + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", medFeeAmt)), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "nonempty min_gas_price with defaultGlobalFee denom, empty global fee, fee lower than min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "empty min_gas_price, empty global fee, empty fee": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.Coins{}, + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + // zero min_gas_price or zero global fee + "zero min_gas_price, zero global fee, zero fee in global fee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt()), sdk.NewCoin("photon", sdk.ZeroInt())), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "zero min_gas_price, zero global fee, empty fee": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.Coins{}, + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + // zero global fee + "zero min_gas_price, zero global fee, zero fee not in globalfee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("stake", sdk.ZeroInt())), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "zero min_gas_price, zero global fee, zero fees one in, one not in globalfee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins( + sdk.NewCoin("stake", sdk.ZeroInt()), + sdk.NewCoin("uatom", sdk.ZeroInt())), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + // zero min_gas_price and empty global fee + "zero min_gas_price, empty global fee, zero fee in min_gas_price_denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.NewCoins(sdk.NewCoin("stake", sdk.ZeroInt())), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "zero min_gas_price, empty global fee, zero fee not in min_gas_price denom, not in defaultZeroGlobalFee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.NewCoins(sdk.NewCoin("quark", sdk.ZeroInt())), + gasLimit: testdata.NewTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "zero min_gas_price, empty global fee, zero fee in defaultZeroGlobalFee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "zero min_gas_price, empty global fee, nonzero fee in defaultZeroGlobalFee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "zero min_gas_price, empty global fee, nonzero fee not in defaultZeroGlobalFee denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsEmpty, + gasPrice: sdk.NewCoins(sdk.NewCoin("quark", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + // empty min_gas_price, zero global fee + "empty min_gas_price, zero global fee, zero fee in global fee denom": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "empty min_gas_price, zero global fee, zero fee not in global fee denom": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("stake", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "empty min_gas_price, zero global fee, nonzero fee in global fee denom": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "empty min_gas_price, zero global fee, nonzero fee not in global fee denom": { + minGasPrice: minGasPriceEmpty, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("stake", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + // zero min_gas_price, nonzero global fee + "zero min_gas_price, nonzero global fee, fee is higher than global fee": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + // nonzero min_gas_price, nonzero global fee + "fee higher/equal than globalfee and min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsHigh, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "fee lower than globalfee and min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsHigh, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "fee with one denom higher/equal, one denom lower than globalfee and min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsNewDenom, + gasPrice: sdk.NewCoins( + sdk.NewCoin("photon", lowFeeAmt), + sdk.NewCoin("quark", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "globalfee > min_gas_price, fee higher/equal than min_gas_price, lower than globalfee": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsHigh, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", medFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "globalfee < min_gas_price, fee higher/equal than globalfee and lower than min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + // nonzero min_gas_price, zero global fee + "nonzero min_gas_price, zero global fee, fee is in global fee denom and lower than min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "nonzero min_gas_price, zero global fee, fee is in global fee denom and higher/equal than min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", medFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "nonzero min_gas_price, zero global fee, fee is in min_gas_price denom which is not in global fee default, but higher/equal than min_gas_price": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParams0, + gasPrice: sdk.NewCoins(sdk.NewCoin("stake", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + // fee denom tests + "min_gas_price denom is not subset of global fee denom , fee paying in global fee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsNewDenom, + gasPrice: sdk.NewCoins(sdk.NewCoin("photon", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "min_gas_price denom is not subset of global fee denom, fee paying in min_gas_price denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsNewDenom, + gasPrice: sdk.NewCoins(sdk.NewCoin("stake", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "fees contain denom not in globalfee": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins( + sdk.NewCoin("uatom", highFeeAmt), + sdk.NewCoin("quark", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "fees contain denom not in globalfee with zero amount": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", highFeeAmt), + sdk.NewCoin("quark", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + // cases from https://github.com/cosmos/gaia/pull/1570#issuecomment-1190524402 + // note: this is kind of a silly scenario but technically correct + // if there is a zero coin in the globalfee, the user could pay 0fees + // if the user includes any fee at all in the non-zero denom, it must be higher than that non-zero fee + // unlikely we will ever see zero and non-zero together but technically possible + "globalfee contains zero coin and non-zero coin, fee is lower than the nonzero coin": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsContain0, + gasPrice: sdk.NewCoins(sdk.NewCoin("photon", lowFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "globalfee contains zero coin, fee contains zero coins of the same denom and a lower fee of the other denom in global fee": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsContain0, + gasPrice: sdk.NewCoins( + sdk.NewCoin("photon", lowFeeAmt), + sdk.NewCoin("uatom", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "globalfee contains zero coin, fee is empty": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsContain0, + gasPrice: sdk.Coins{}, + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "globalfee contains zero coin, fee contains lower fee of zero coins's denom, globalfee also contains nonzero coin,fee contains higher fee of nonzero coins's denom, ": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsContain0, + gasPrice: sdk.NewCoins( + sdk.NewCoin("photon", lowFeeAmt), + sdk.NewCoin("uatom", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "globalfee contains zero coin, fee is all zero coins but in global fee's denom": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsContain0, + gasPrice: sdk.NewCoins( + sdk.NewCoin("photon", sdk.ZeroInt()), + sdk.NewCoin("uatom", sdk.ZeroInt()), + ), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "globalfee contains zero coin, fee is higher than the nonzero coin": { + minGasPrice: minGasPrice0, + globalFeeParams: globalfeeParamsContain0, + gasPrice: sdk.NewCoins(sdk.NewCoin("photon", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + // test bypass msg + "msg type ibc, zero fee in globalfee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: ibcchanneltypes.NewMsgRecvPacket( + ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""), + txCheck: true, + expErr: false, + }, + "msg type ibc, zero fee not in globalfee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("photon", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: ibcchanneltypes.NewMsgRecvPacket( + ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""), + txCheck: true, + expErr: false, + }, + "msg type ibc, nonzero fee in globalfee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: ibcchanneltypes.NewMsgRecvPacket( + ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""), + txCheck: true, + expErr: false, + }, + "msg type ibc, nonzero fee not in globalfee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("photon", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: ibcchanneltypes.NewMsgRecvPacket( + ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""), + txCheck: true, + expErr: true, + }, + "msg type ibc, empty fee": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.Coins{}, + gasLimit: newTestGasLimit(), + txMsg: ibcchanneltypes.NewMsgRecvPacket( + ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""), + txCheck: true, + expErr: false, + }, + "msg type non-ibc, nonzero fee in globalfee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: false, + }, + "msg type non-ibc, empty fee": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.Coins{}, + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "msg type non-ibc, nonzero fee not in globalfee denom": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("photon", highFeeAmt)), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: true, + expErr: true, + }, + "disable checkTx: no fee check. min_gas_price is low, global fee is low, tx fee is zero": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: false, + expErr: false, + }, + "disable checkTx: no fee check. min_gas_price is low, global fee is low, tx fee's denom is not in global fees denoms set": { + minGasPrice: minGasPrice, + globalFeeParams: globalfeeParamsLow, + gasPrice: sdk.NewCoins(sdk.NewCoin("quark", sdk.ZeroInt())), + gasLimit: newTestGasLimit(), + txMsg: testdata.NewTestMsg(addr1), + txCheck: false, + expErr: false, + }, + } + for name, testCase := range testCases { + s.Run(name, func() { + // set globalfees and min gas price + globalfeeSubspace := s.SetupTestGlobalFeeStoreAndMinGasPrice(testCase.minGasPrice, testCase.globalFeeParams) + stakingParam := stakingtypes.DefaultParams() + stakingParam.BondDenom = "uatom" + stakingSubspace := s.SetupTestStakingSubspace(stakingParam) + // setup antehandler + mfd := gaiafeeante.NewFeeDecorator(migalooapp.GetDefaultBypassFeeMessages(), globalfeeSubspace, stakingSubspace, newTestGasLimit()) + antehandler := sdk.ChainAnteDecorators(mfd) + + s.Require().NoError(s.txBuilder.SetMsgs(testCase.txMsg)) + s.txBuilder.SetFeeAmount(testCase.gasPrice) + s.txBuilder.SetGasLimit(testCase.gasLimit) + tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID()) + s.Require().NoError(err) + + s.ctx = s.ctx.WithIsCheckTx(testCase.txCheck) + _, err = antehandler(s.ctx, tx, false) + if !testCase.expErr { + s.Require().NoError(err) + } else { + s.Require().Error(err) + } + }) + } +} + +// helpers +func newTestGasLimit() uint64 { + return 200000 +} diff --git a/x/globalfee/ante/antetest/fee_test_setup.go b/x/globalfee/ante/antetest/fee_test_setup.go new file mode 100644 index 00000000..a2cd658c --- /dev/null +++ b/x/globalfee/ante/antetest/fee_test_setup.go @@ -0,0 +1,109 @@ +package antetest + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/params/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/suite" + tmrand "github.com/tendermint/tendermint/libs/rand" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + migalooaapp "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/app" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee" + globfeetypes "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +type IntegrationTestSuite struct { + suite.Suite + + app *migalooaapp.MigalooApp + ctx sdk.Context + clientCtx client.Context + txBuilder client.TxBuilder +} + +func (s *IntegrationTestSuite) SetupTest() { + app := migalooaapp.SetupMigalooAppWithValSet(s.T()) + ctx := app.BaseApp.NewContext(false, tmproto.Header{ + ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)), + Height: 1, + }) + + encodingConfig := migalooaapp.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 (s *IntegrationTestSuite) SetupTestGlobalFeeStoreAndMinGasPrice(minGasPrice []sdk.DecCoin, globalFeeParams *globfeetypes.Params) types.Subspace { + subspace := s.app.GetSubspace(globalfee.ModuleName) + subspace.SetParamSet(s.ctx, globalFeeParams) + s.ctx = s.ctx.WithMinGasPrices(minGasPrice).WithIsCheckTx(true) + + return subspace +} + +// SetupTestStakingSubspace sets uatom as bond denom for the fee tests. +func (s *IntegrationTestSuite) SetupTestStakingSubspace(params stakingtypes.Params) types.Subspace { + s.app.GetSubspace(stakingtypes.ModuleName).SetParamSet(s.ctx, ¶ms) + return s.app.GetSubspace(stakingtypes.ModuleName) +} + +func (s *IntegrationTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { + var sigsV2 []signing.SignatureV2 + for i, priv := range privs { + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: s.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + + if err := s.txBuilder.SetSignatures(sigsV2...); err != nil { + return nil, err + } + + sigsV2 = []signing.SignatureV2{} + for i, priv := range privs { + signerData := xauthsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + sigV2, err := tx.SignWithPrivKey( + s.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + signerData, + s.txBuilder, + priv, + s.clientCtx.TxConfig, + accSeqs[i], + ) + if err != nil { + return nil, err + } + + sigsV2 = append(sigsV2, sigV2) + } + + if err := s.txBuilder.SetSignatures(sigsV2...); err != nil { + return nil, err + } + + return s.txBuilder.GetTx(), nil +} diff --git a/x/globalfee/ante/antetest/fee_utils_test.go b/x/globalfee/ante/antetest/fee_utils_test.go new file mode 100644 index 00000000..8d8c702d --- /dev/null +++ b/x/globalfee/ante/antetest/fee_utils_test.go @@ -0,0 +1,415 @@ +package antetest + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/ante" +) + +type feeUtilsTestSuite struct { + suite.Suite +} + +func TestFeeUtilsTestSuite(t *testing.T) { + suite.Run(t, new(feeUtilsTestSuite)) +} + +func (s *feeUtilsTestSuite) TestContainZeroCoins() { + zeroCoin1 := sdk.NewCoin("photon", sdk.ZeroInt()) + zeroCoin2 := sdk.NewCoin("stake", sdk.ZeroInt()) + coin1 := sdk.NewCoin("photon", sdk.NewInt(1)) + coin2 := sdk.NewCoin("stake", sdk.NewInt(2)) + coin3 := sdk.NewCoin("quark", sdk.NewInt(3)) + // coins must be valid !!! + coinsEmpty := sdk.Coins{} + coinsNonEmpty := sdk.Coins{coin1, coin2} + coinsCointainZero := sdk.Coins{coin1, zeroCoin2} + coinsCointainTwoZero := sdk.Coins{zeroCoin1, zeroCoin2, coin3} + coinsAllZero := sdk.Coins{zeroCoin1, zeroCoin2} + + tests := []struct { + c sdk.Coins + ok bool + }{ + { + coinsEmpty, + true, + }, + { + coinsNonEmpty, + false, + }, + { + coinsCointainZero, + true, + }, + { + coinsCointainTwoZero, + true, + }, + { + coinsAllZero, + true, + }, + } + + for _, test := range tests { + ok := ante.ContainZeroCoins(test.c) + s.Require().Equal(test.ok, ok) + } +} + +func (s *feeUtilsTestSuite) TestCombinedFeeRequirement() { + zeroCoin1 := sdk.NewCoin("photon", sdk.ZeroInt()) + zeroCoin2 := sdk.NewCoin("stake", sdk.ZeroInt()) + zeroCoin3 := sdk.NewCoin("quark", sdk.ZeroInt()) + coin1 := sdk.NewCoin("photon", sdk.NewInt(1)) + coin2 := sdk.NewCoin("stake", sdk.NewInt(2)) + coin1High := sdk.NewCoin("photon", sdk.NewInt(10)) + coin2High := sdk.NewCoin("stake", sdk.NewInt(20)) + coinNewDenom1 := sdk.NewCoin("Newphoton", sdk.NewInt(1)) + coinNewDenom2 := sdk.NewCoin("Newstake", sdk.NewInt(1)) + // coins must be valid !!! and sorted!!! + coinsEmpty := sdk.Coins{} + coinsNonEmpty := sdk.Coins{coin1, coin2}.Sort() + coinsNonEmptyHigh := sdk.Coins{coin1High, coin2High}.Sort() + coinsNonEmptyOneHigh := sdk.Coins{coin1High, coin2}.Sort() + coinsNewDenom := sdk.Coins{coinNewDenom1, coinNewDenom2}.Sort() + coinsNewOldDenom := sdk.Coins{coin1, coinNewDenom1}.Sort() + coinsNewOldDenomHigh := sdk.Coins{coin1High, coinNewDenom1}.Sort() + coinsCointainZero := sdk.Coins{coin1, zeroCoin2}.Sort() + coinsCointainZeroNewDenom := sdk.Coins{coin1, zeroCoin3}.Sort() + coinsAllZero := sdk.Coins{zeroCoin1, zeroCoin2}.Sort() + tests := map[string]struct { + cGlobal sdk.Coins + c sdk.Coins + combined sdk.Coins + }{ + "global fee empty, min fee empty, combined fee empty": { + cGlobal: coinsEmpty, + c: coinsEmpty, + combined: coinsEmpty, + }, + "global fee empty, min fee nonempty, combined fee empty": { + cGlobal: coinsEmpty, + c: coinsNonEmpty, + combined: coinsEmpty, + }, + "global fee nonempty, min fee empty, combined fee = global fee": { + cGlobal: coinsNonEmpty, + c: coinsNonEmpty, + combined: coinsNonEmpty, + }, + "global fee and min fee have overlapping denom, min fees amounts are all higher": { + cGlobal: coinsNonEmpty, + c: coinsNonEmptyHigh, + combined: coinsNonEmptyHigh, + }, + "global fee and min fee have overlapping denom, one of min fees amounts is higher": { + cGlobal: coinsNonEmpty, + c: coinsNonEmptyOneHigh, + combined: coinsNonEmptyOneHigh, + }, + "global fee and min fee have no overlapping denom, combined fee = global fee": { + cGlobal: coinsNonEmpty, + c: coinsNewDenom, + combined: coinsNonEmpty, + }, + "global fees and min fees have partial overlapping denom, min fee amount <= global fee amount, combined fees = global fees": { + cGlobal: coinsNonEmpty, + c: coinsNewOldDenom, + combined: coinsNonEmpty, + }, + "global fees and min fees have partial overlapping denom, one min fee amount > global fee amount, combined fee = overlapping highest": { + cGlobal: coinsNonEmpty, + c: coinsNewOldDenomHigh, + combined: sdk.Coins{coin1High, coin2}, + }, + "global fees have zero fees, min fees have overlapping non-zero fees, combined fees = overlapping highest": { + cGlobal: coinsCointainZero, + c: coinsNonEmpty, + combined: sdk.Coins{coin1, coin2}, + }, + "global fees have zero fees, min fees have overlapping zero fees": { + cGlobal: coinsCointainZero, + c: coinsCointainZero, + combined: coinsCointainZero, + }, + "global fees have zero fees, min fees have non-overlapping zero fees": { + cGlobal: coinsCointainZero, + c: coinsCointainZeroNewDenom, + combined: coinsCointainZero, + }, + "global fees are all zero fees, min fees have overlapping zero fees": { + cGlobal: coinsAllZero, + c: coinsAllZero, + combined: coinsAllZero, + }, + "global fees are all zero fees, min fees have overlapping non-zero fees, combined fee = overlapping highest": { + cGlobal: coinsAllZero, + c: coinsCointainZeroNewDenom, + combined: sdk.Coins{coin1, zeroCoin2}, + }, + "global fees are all zero fees, fees have one overlapping non-zero fee": { + cGlobal: coinsAllZero, + c: coinsCointainZero, + combined: coinsCointainZero, + }, + } + + for name, test := range tests { + s.Run(name, func() { + allFees := ante.CombinedFeeRequirement(test.cGlobal, test.c) + s.Require().Equal(test.combined, allFees) + }) + } +} + +func (s *feeUtilsTestSuite) TestDenomsSubsetOfIncludingZero() { + emptyCoins := sdk.Coins{} + + zeroCoin1 := sdk.NewCoin("photon", sdk.ZeroInt()) + zeroCoin2 := sdk.NewCoin("stake", sdk.ZeroInt()) + zeroCoin3 := sdk.NewCoin("quark", sdk.ZeroInt()) + + coin1 := sdk.NewCoin("photon", sdk.NewInt(1)) + coin2 := sdk.NewCoin("stake", sdk.NewInt(2)) + coin3 := sdk.NewCoin("quark", sdk.NewInt(3)) + + coinNewDenom1 := sdk.NewCoin("newphoton", sdk.NewInt(1)) + coinNewDenom2 := sdk.NewCoin("newstake", sdk.NewInt(2)) + coinNewDenom3 := sdk.NewCoin("newquark", sdk.NewInt(3)) + coinNewDenom1Zero := sdk.NewCoin("newphoton", sdk.ZeroInt()) + // coins must be valid !!! and sorted!!! + coinsAllZero := sdk.Coins{zeroCoin1, zeroCoin2, zeroCoin3}.Sort() + coinsAllZeroShort := sdk.Coins{zeroCoin1, zeroCoin2}.Sort() + coinsContainZero := sdk.Coins{zeroCoin1, zeroCoin2, coin3}.Sort() + coinsContainZeroNewDenoms := sdk.Coins{zeroCoin1, zeroCoin2, coinNewDenom1Zero}.Sort() + coins := sdk.Coins{coin1, coin2, coin3}.Sort() + coinsShort := sdk.Coins{coin1, coin2}.Sort() + coinsAllNewDenom := sdk.Coins{coinNewDenom1, coinNewDenom2, coinNewDenom3}.Sort() + coinsOldNewDenom := sdk.Coins{coin1, coin2, coinNewDenom1}.Sort() + + tests := map[string]struct { + superset sdk.Coins + set sdk.Coins + subset bool + }{ + "empty coins is a DenomsSubsetOf empty coins": { + superset: emptyCoins, + set: emptyCoins, + subset: true, + }, + "nonempty coins is not a DenomsSubsetOf empty coins": { + superset: emptyCoins, + set: coins, + subset: false, + }, + "empty coins is not a DenomsSubsetOf nonempty, nonzero coins": { + superset: emptyCoins, + set: coins, + subset: false, + }, + "empty coins is a DenomsSubsetOf coins of all zeros": { + superset: coinsAllZero, + set: emptyCoins, + subset: true, + }, + "empty coins is a DenomsSubsetOf coinsContainZero": { + superset: coinsContainZero, + set: emptyCoins, + subset: true, + }, + "two sets no denoms overlapping, DenomsSubsetOf = false": { + superset: coins, + set: coinsAllNewDenom, + subset: false, + }, + "two sets have partially overlapping denoms, DenomsSubsetOf = false": { + superset: coins, + set: coinsOldNewDenom, + subset: false, + }, + "two sets are nonzero, set's denoms are all in superset, DenomsSubsetOf = true": { + superset: coins, + set: coinsShort, + subset: true, + }, + "supersets are zero coins, set's denoms are all in superset, DenomsSubsetOf = true": { + superset: coinsAllZero, + set: coinsShort, + subset: true, + }, + "supersets contains zero coins, set's denoms are all in superset, DenomsSubsetOf = true": { + superset: coinsContainZero, + set: coinsShort, + subset: true, + }, + "supersets contains zero coins, set's denoms contains zero coins, denoms are overlapping DenomsSubsetOf = true": { + superset: coinsContainZero, + set: coinsContainZero, + subset: true, + }, + "supersets contains zero coins, set's denoms contains zero coins, denoms are not overlapping DenomsSubsetOf = false": { + superset: coinsContainZero, + set: coinsContainZeroNewDenoms, + subset: false, + }, + "two sets of all zero coins, have the same denoms, DenomsSubsetOf = true": { + superset: coinsAllZero, + set: coinsAllZeroShort, + subset: true, + }, + } + + for name, test := range tests { + s.Run(name, func() { + subset := ante.DenomsSubsetOfIncludingZero(test.set, test.superset) + s.Require().Equal(test.subset, subset) + }) + } +} + +func (s *feeUtilsTestSuite) TestIsAnyGTEIncludingZero() { + emptyCoins := sdk.Coins{} + + zeroCoin1 := sdk.NewCoin("photon", sdk.ZeroInt()) + zeroCoin2 := sdk.NewCoin("stake", sdk.ZeroInt()) + zeroCoin3 := sdk.NewCoin("quark", sdk.ZeroInt()) + + coin1 := sdk.NewCoin("photon", sdk.NewInt(10)) + coin1Low := sdk.NewCoin("photon", sdk.NewInt(1)) + coin1High := sdk.NewCoin("photon", sdk.NewInt(100)) + coin2 := sdk.NewCoin("stake", sdk.NewInt(20)) + coin2Low := sdk.NewCoin("stake", sdk.NewInt(2)) + coin2High := sdk.NewCoin("stake", sdk.NewInt(200)) + coin3 := sdk.NewCoin("quark", sdk.NewInt(30)) + + coinNewDenom1 := sdk.NewCoin("newphoton", sdk.NewInt(10)) + coinNewDenom2 := sdk.NewCoin("newstake", sdk.NewInt(20)) + coinNewDenom3 := sdk.NewCoin("newquark", sdk.NewInt(30)) + zeroCoinNewDenom1 := sdk.NewCoin("newphoton", sdk.NewInt(10)) + zeroCoinNewDenom2 := sdk.NewCoin("newstake", sdk.NewInt(20)) + zeroCoinNewDenom3 := sdk.NewCoin("newquark", sdk.NewInt(30)) + // coins must be valid !!! and sorted!!! + coinsAllZero := sdk.Coins{zeroCoin1, zeroCoin2, zeroCoin3}.Sort() + coinsAllNewDenomAllZero := sdk.Coins{zeroCoinNewDenom1, zeroCoinNewDenom2, zeroCoinNewDenom3}.Sort() + coinsAllZeroShort := sdk.Coins{zeroCoin1, zeroCoin2}.Sort() + coinsContainZero := sdk.Coins{zeroCoin1, zeroCoin2, coin3}.Sort() + + coins := sdk.Coins{coin1, coin2, coin3}.Sort() + coinsHighHigh := sdk.Coins{coin1High, coin2High} + coinsHighLow := sdk.Coins{coin1High, coin2Low}.Sort() + coinsLowLow := sdk.Coins{coin1Low, coin2Low}.Sort() + // coinsShort := sdk.Coins{coin1, coin2}.Sort() + coinsAllNewDenom := sdk.Coins{coinNewDenom1, coinNewDenom2, coinNewDenom3}.Sort() + coinsOldNewDenom := sdk.Coins{coin1, coinNewDenom1, coinNewDenom2}.Sort() + coinsOldLowNewDenom := sdk.Coins{coin1Low, coinNewDenom1, coinNewDenom2}.Sort() + tests := map[string]struct { + c1 sdk.Coins + c2 sdk.Coins + gte bool // greater or equal + }{ + "zero coins are GTE zero coins": { + c1: coinsAllZero, + c2: coinsAllZero, + gte: true, + }, + "zero coins(short) are GTE zero coins": { + c1: coinsAllZero, + c2: coinsAllZeroShort, + gte: true, + }, + "zero coins are GTE zero coins(short)": { + c1: coinsAllZeroShort, + c2: coinsAllZero, + gte: true, + }, + "c2 is all zero coins, with different denoms from c1 which are all zero coins too": { + c1: coinsAllZero, + c2: coinsAllNewDenomAllZero, + gte: false, + }, + "empty coins are GTE empty coins": { + c1: emptyCoins, + c2: emptyCoins, + gte: true, + }, + "empty coins are GTE zero coins": { + c1: coinsAllZero, + c2: emptyCoins, + gte: true, + }, + "empty coins are GTE coins that contain zero denom": { + c1: coinsContainZero, + c2: emptyCoins, + gte: true, + }, + "zero coins are not GTE empty coins": { + c1: emptyCoins, + c2: coinsAllZero, + gte: false, + }, + "empty coins are not GTE nonzero coins": { + c1: coins, + c2: emptyCoins, + gte: false, + }, + // special case, not the opposite result of the above case + "nonzero coins are not GTE empty coins": { + c1: emptyCoins, + c2: coins, + gte: false, + }, + "nonzero coins are GTE zero coins, has overlapping denom": { + c1: coinsAllZero, + c2: coins, + gte: true, + }, + "nonzero coins are GTE coins contain zero coins, zero coin is overlapping denom": { + c1: coinsContainZero, + c2: coins, + gte: true, + }, + "one denom amount higher, one denom amount lower": { + c1: coins, + c2: coinsHighLow, + gte: true, + }, + "all coins amounts are lower, denom overlapping": { + c1: coins, + c2: coinsLowLow, + gte: false, + }, + "all coins amounts are higher, denom overlapping": { + c1: coins, + c2: coinsHighHigh, + gte: true, + }, + "denoms are all not overlapping": { + c1: coins, + c2: coinsAllNewDenom, + gte: false, + }, + "denom not all overlapping, one overlapping denom is gte": { + c1: coins, + c2: coinsOldNewDenom, + gte: true, + }, + "denom not all overlapping, the only one overlapping denom is smaller": { + c1: coins, + c2: coinsOldLowNewDenom, + gte: false, + }, + } + + for name, test := range tests { + s.Run(name, func() { + gte := ante.IsAnyGTEIncludingZero(test.c2, test.c1) + s.Require().Equal(test.gte, gte) + }) + } +} diff --git a/x/globalfee/ante/fee.go b/x/globalfee/ante/fee.go new file mode 100644 index 00000000..64f29745 --- /dev/null +++ b/x/globalfee/ante/fee.go @@ -0,0 +1,161 @@ +package ante + +import ( + "errors" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee" +) + +// FeeWithBypassDecorator will check if the transaction's fee is at least as large +// as the local validator's minimum gasFee (defined in validator config) and global fee, and the fee denom should be in the global fees' denoms. +// +// If fee is too low, decorator returns error and tx is rejected from mempool. +// Note this only applies when ctx.CheckTx = true. If fee is high enough or not +// CheckTx, then call next AnteHandler. +// +// CONTRACT: Tx must implement FeeTx to use FeeDecorator +// If the tx msg type is one of the bypass msg types, the tx is valid even if the min fee is lower than normally required. +// If the bypass tx still carries fees, the fee denom should be the same as global fee required. + +var _ sdk.AnteDecorator = FeeDecorator{} + +type FeeDecorator struct { + BypassMinFeeMsgTypes []string + GlobalMinFee globalfee.ParamSource + StakingSubspace paramtypes.Subspace + MaxBypassMinFeeMsgGasUsage uint64 +} + +func NewFeeDecorator(bypassMsgTypes []string, globalfeeSubspace, stakingSubspace paramtypes.Subspace, maxBypassMinFeeMsgGasUsage uint64) FeeDecorator { + if !globalfeeSubspace.HasKeyTable() { + panic("global fee paramspace was not set up via module") + } + + if !stakingSubspace.HasKeyTable() { + panic("staking paramspace was not set up via module") + } + + return FeeDecorator{ + BypassMinFeeMsgTypes: bypassMsgTypes, + GlobalMinFee: globalfeeSubspace, + StakingSubspace: stakingSubspace, + MaxBypassMinFeeMsgGasUsage: maxBypassMinFeeMsgGasUsage, + } +} + +func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // please note: after parsing feeflag, the zero fees are removed already + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + feeCoins := feeTx.GetFee().Sort() + gas := feeTx.GetGas() + msgs := feeTx.GetMsgs() + + // Accept zero fee transactions only if both of the following statements are true: + // - the tx contains only message types that can bypass the minimum fee, + // see BypassMinFeeMsgTypes; + // - the total gas limit per message does not exceed MaxBypassMinFeeMsgGasUsage, + // i.e., totalGas <= len(msgs) * MaxBypassMinFeeMsgGasUsage + // Otherwise, minimum fees and global fees are checked to prevent spam. + containsOnlyBypassMinFeeMsgs := mfd.bypassMinFeeMsgs(msgs) + doesNotExceedMaxGasUsage := gas <= uint64(len(msgs))*mfd.MaxBypassMinFeeMsgGasUsage + allowedToBypassMinFee := containsOnlyBypassMinFeeMsgs && doesNotExceedMaxGasUsage + + var allFees sdk.Coins + requiredGlobalFees, err := mfd.getGlobalFee(ctx, feeTx) + if err != nil { + panic(err) + } + requiredFees := getMinGasPrice(ctx, feeTx) + + // Only check for minimum fees and global fee if the execution mode is CheckTx + if !ctx.IsCheckTx() || simulate { + return next(ctx, tx, simulate) + } + + if !allowedToBypassMinFee { + // Either the transaction contains at least on message of a type + // that cannot bypass the minimum fee or the total gas limit exceeds + // the imposed threshold. As a result, check that the fees are in + // expected denominations and the amounts are greater or equal than + // the expected amounts. + + allFees = CombinedFeeRequirement(requiredGlobalFees, requiredFees) + + // Check that the fees are in expected denominations. Note that a zero fee + // is accepted if the global fee has an entry with a zero amount, e.g., 0uatoms. + if !DenomsSubsetOfIncludingZero(feeCoins, allFees) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, allFees) + } + // Check that the amounts of the fees are greater or equal than + // the expected amounts, i.e., at least one feeCoin amount must + // be greater or equal to one of the combined required fees. + if !IsAnyGTEIncludingZero(feeCoins, allFees) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, allFees) + } + } else { + // Transactions with zero fees are accepted + if len(feeCoins) == 0 { + return next(ctx, tx, simulate) + } + // If the transaction fee is non-zero, then check that the fees are in + // expected denominations. + if !DenomsSubsetOfIncludingZero(feeCoins, requiredGlobalFees) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fees denom is wrong; got: %s required: %s", feeCoins, requiredGlobalFees) + } + } + + return next(ctx, tx, simulate) +} + +// ParamStoreKeyMinGasPrices type require coins sorted. getGlobalFee will also return sorted coins (might return 0denom if globalMinGasPrice is 0) +func (mfd FeeDecorator) getGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, error) { + var ( + globalMinGasPrices sdk.DecCoins + err error + ) + + if mfd.GlobalMinFee.Has(ctx, types.ParamStoreKeyMinGasPrices) { + mfd.GlobalMinFee.Get(ctx, types.ParamStoreKeyMinGasPrices, &globalMinGasPrices) + } + // global fee is empty set, set global fee to 0uatom + if len(globalMinGasPrices) == 0 { + globalMinGasPrices, err = mfd.DefaultZeroGlobalFee(ctx) + } + requiredGlobalFees := make(sdk.Coins, len(globalMinGasPrices)) + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdk.NewDec(int64(feeTx.GetGas())) + for i, gp := range globalMinGasPrices { + fee := gp.Amount.Mul(glDec) + requiredGlobalFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + return requiredGlobalFees.Sort(), err +} + +func (mfd FeeDecorator) DefaultZeroGlobalFee(ctx sdk.Context) ([]sdk.DecCoin, error) { + bondDenom := mfd.getBondDenom(ctx) + if bondDenom == "" { + return nil, errors.New("empty staking bond denomination") + } + + return []sdk.DecCoin{sdk.NewDecCoinFromDec(bondDenom, sdk.NewDec(0))}, nil +} + +func (mfd FeeDecorator) getBondDenom(ctx sdk.Context) string { + var bondDenom string + if mfd.StakingSubspace.Has(ctx, stakingtypes.KeyBondDenom) { + mfd.StakingSubspace.Get(ctx, stakingtypes.KeyBondDenom, &bondDenom) + } + + return bondDenom +} diff --git a/x/globalfee/ante/fee_utils.go b/x/globalfee/ante/fee_utils.go new file mode 100644 index 00000000..7e41bceb --- /dev/null +++ b/x/globalfee/ante/fee_utils.go @@ -0,0 +1,198 @@ +package ante + +import ( + "math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// getMinGasPrice will also return sorted coins +func getMinGasPrice(ctx sdk.Context, feeTx sdk.FeeTx) sdk.Coins { + minGasPrices := ctx.MinGasPrices() + gas := feeTx.GetGas() + // special case: if minGasPrices=[], requiredFees=[] + requiredFees := make(sdk.Coins, len(minGasPrices)) + // if not all coins are zero, check fee with min_gas_price + if !minGasPrices.IsZero() { + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdk.NewDec(int64(gas)) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + } + + return requiredFees.Sort() +} + +func (mfd FeeDecorator) bypassMinFeeMsgs(msgs []sdk.Msg) bool { + for _, msg := range msgs { + if StringInSlice(sdk.MsgTypeURL(msg), mfd.BypassMinFeeMsgTypes) { + continue + } + return false + } + + return true +} + +// StringInSlice returns true if a is found the list. +func StringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// DenomsSubsetOfIncludingZero and IsAnyGTEIncludingZero are similar to DenomsSubsetOf and IsAnyGTE in sdk. Since we allow zero coins in global fee(zero coins means the chain does not want to set a global fee but still want to define the fee's denom) +// +// overwrite DenomsSubsetOfIncludingZero from sdk, to allow zero amt coins in superset. e.g. 1stake is DenomsSubsetOfIncludingZero 0stake. [] is the DenomsSubsetOfIncludingZero of [0stake] but not [1stake]. +// DenomsSubsetOfIncludingZero returns true if coins's denoms is subset of coinsB's denoms. +// if coins is empty set, empty set is any sets' subset +func DenomsSubsetOfIncludingZero(coins, coinsB sdk.Coins) bool { + // more denoms in B than in receiver + if len(coins) > len(coinsB) { + return false + } + // coins=[], coinsB=[0stake] + // let all len(coins) == 0 pass and reject later at IsAnyGTEIncludingZero + if len(coins) == 0 && ContainZeroCoins(coinsB) { + return true + } + // coins=1stake, coinsB=[0stake,1uatom] + for _, coin := range coins { + err := sdk.ValidateDenom(coin.Denom) + if err != nil { + panic(err) + } + if ok, _ := Find(coinsB, coin.Denom); !ok { + return false + } + } + + return true +} + +// overwrite the IsAnyGTEIncludingZero from sdk to allow zero coins in coins and coinsB. +// IsAnyGTEIncludingZero returns true if coins contain at least one denom that is present at a greater or equal amount in coinsB; it returns false otherwise. +// if CoinsB is emptyset, no coins sets are IsAnyGTEIncludingZero coinsB unless coins is also empty set. +// NOTE: IsAnyGTEIncludingZero operates under the invariant that both coin sets are sorted by denoms. +// contract !!!! coins must be DenomsSubsetOfIncludingZero of coinsB +func IsAnyGTEIncludingZero(coins, coinsB sdk.Coins) bool { + // no set is empty set's subset except empty set + // this is different from sdk, sdk return false for coinsB empty + if len(coinsB) == 0 && len(coins) == 0 { + return true + } + // nothing is gte empty coins + if len(coinsB) == 0 && len(coins) != 0 { + return false + } + // if feecoins empty (len(coins)==0 && len(coinsB) != 0 ), and globalfee has one denom of amt zero, return true + if len(coins) == 0 { + return ContainZeroCoins(coinsB) + } + + // len(coinsB) != 0 && len(coins) != 0 + // special case: coins=1stake, coinsB=[2stake,0uatom], fail + for _, coin := range coins { + // not find coin in CoinsB + if ok, _ := Find(coinsB, coin.Denom); ok { + // find coin in coinsB, and if the amt == 0, mean either coin=0denom or coinsB=0denom...both true + amt := coinsB.AmountOf(coin.Denom) + if coin.Amount.GTE(amt) { + return true + } + } + } + + return false +} + +// return true if coinsB is empty or contains zero coins, +// CoinsB must be validate coins !!! +func ContainZeroCoins(coinsB sdk.Coins) bool { + if len(coinsB) == 0 { + return true + } + for _, coin := range coinsB { + if coin.IsZero() { + return true + } + } + + return false +} + +// CombinedFeeRequirement will combine the global fee and min_gas_price. Both globalFees and minGasPrices must be valid, but CombinedFeeRequirement does not validate them, so it may return 0denom. +func CombinedFeeRequirement(globalFees, minGasPrices sdk.Coins) sdk.Coins { + // empty min_gas_price + if len(minGasPrices) == 0 { + return globalFees + } + // empty global fee is not possible if we set default global fee + if len(globalFees) == 0 && len(minGasPrices) != 0 { + return globalFees + } + + // if min_gas_price denom is in globalfee, and the amount is higher than globalfee, add min_gas_price to allFees + var allFees sdk.Coins + for _, fee := range globalFees { + // min_gas_price denom in global fee + ok, c := Find(minGasPrices, fee.Denom) + if ok && c.Amount.GT(fee.Amount) { + allFees = append(allFees, c) + } else { + allFees = append(allFees, fee) + } + } + + return allFees.Sort() +} + +// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the fee +// provided in a transaction. +func GetTxPriority(fee sdk.Coins) int64 { + var priority int64 + for _, c := range fee { + p := int64(math.MaxInt64) + if c.Amount.IsInt64() { + p = c.Amount.Int64() + } + if priority == 0 || p < priority { + priority = p + } + } + + return priority +} + +// Find replaces the functionality of Coins.Find from SDK v0.46.x +func Find(coins sdk.Coins, denom string) (bool, sdk.Coin) { + switch len(coins) { + case 0: + return false, sdk.Coin{} + + case 1: + coin := coins[0] + if coin.Denom == denom { + return true, coin + } + return false, sdk.Coin{} + + default: + midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 + coin := coins[midIdx] + switch { + case denom < coin.Denom: + return Find(coins[:midIdx], denom) + case denom == coin.Denom: + return true, coin + default: + return Find(coins[midIdx+1:], denom) + } + } +} diff --git a/x/globalfee/client/cli/query.go b/x/globalfee/client/cli/query.go new file mode 100644 index 00000000..606f91c8 --- /dev/null +++ b/x/globalfee/client/cli/query.go @@ -0,0 +1,48 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the global fee module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + queryCmd.AddCommand( + GetCmdShowMinimumGasPrices(), + ) + return queryCmd +} + +func GetCmdShowMinimumGasPrices() *cobra.Command { + cmd := &cobra.Command{ + Use: "minimum-gas-prices", + Short: "Show minimum gas prices", + Long: "Show all minimum gas prices", + Aliases: []string{"min"}, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.MinimumGasPrices(cmd.Context(), &types.QueryMinimumGasPricesRequest{}) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/globalfee/genesis_test.go b/x/globalfee/genesis_test.go new file mode 100644 index 00000000..5800560c --- /dev/null +++ b/x/globalfee/genesis_test.go @@ -0,0 +1,127 @@ +package globalfee + +import ( + "testing" + "time" + + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +func TestDefaultGenesis(t *testing.T) { + encCfg := simappparams.MakeTestEncodingConfig() + gotJSON := AppModuleBasic{}.DefaultGenesis(encCfg.Codec) + assert.JSONEq(t, `{"params":{"minimum_gas_prices":[]}}`, string(gotJSON), string(gotJSON)) +} + +func TestValidateGenesis(t *testing.T) { + encCfg := simappparams.MakeTestEncodingConfig() + specs := map[string]struct { + src string + expErr bool + }{ + "all good": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"1"}]}}`, + }, + "empty minimum": { + src: `{"params":{"minimum_gas_prices":[]}}`, + }, + "minimum not set": { + src: `{"params":{}}`, + }, + "zero amount allowed": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"0"}]}}`, + expErr: false, + }, + "duplicate denoms not allowed": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"1"},{"denom":"ALX", "amount":"2"}]}}`, + expErr: true, + }, + "negative amounts not allowed": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"-1"}]}}`, + expErr: true, + }, + "denom must be sorted": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ZLX", "amount":"1"},{"denom":"ALX", "amount":"2"}]}}`, + expErr: true, + }, + "sorted denoms is allowed": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"1"},{"denom":"ZLX", "amount":"2"}]}}`, + expErr: false, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + gotErr := AppModuleBasic{}.ValidateGenesis(encCfg.Codec, nil, []byte(spec.src)) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + }) + } +} + +func TestInitExportGenesis(t *testing.T) { + specs := map[string]struct { + src string + exp types.GenesisState + }{ + "single fee": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"1"}]}}`, + exp: types.GenesisState{Params: types.Params{MinimumGasPrices: sdk.NewDecCoins(sdk.NewDecCoin("ALX", sdk.NewInt(1)))}}, + }, + "multiple fee options": { + src: `{"params":{"minimum_gas_prices":[{"denom":"ALX", "amount":"1"}, {"denom":"BLX", "amount":"0.001"}]}}`, + exp: types.GenesisState{Params: types.Params{MinimumGasPrices: sdk.NewDecCoins(sdk.NewDecCoin("ALX", sdk.NewInt(1)), + sdk.NewDecCoinFromDec("BLX", sdk.NewDecWithPrec(1, 3)))}}, + }, + "no fee set": { + src: `{"params":{}}`, + exp: types.GenesisState{Params: types.Params{MinimumGasPrices: sdk.DecCoins{}}}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx, encCfg, subspace := setupTestStore(t) + m := NewAppModule(subspace) + m.InitGenesis(ctx, encCfg.Codec, []byte(spec.src)) + gotJSON := m.ExportGenesis(ctx, encCfg.Codec) + var got types.GenesisState + require.NoError(t, encCfg.Codec.UnmarshalJSON(gotJSON, &got)) + assert.Equal(t, spec.exp, got, string(gotJSON)) + }) + } +} + +func setupTestStore(t *testing.T) (sdk.Context, simappparams.EncodingConfig, paramstypes.Subspace) { + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + encCfg := simappparams.MakeTestEncodingConfig() + keyParams := sdk.NewKVStoreKey(paramstypes.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(paramstypes.TStoreKey) + ms.MountStoreWithDB(keyParams, storetypes.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, storetypes.StoreTypeTransient, db) + require.NoError(t, ms.LoadLatestVersion()) + + paramsKeeper := paramskeeper.NewKeeper(encCfg.Codec, encCfg.Amino, keyParams, tkeyParams) + + ctx := sdk.NewContext(ms, tmproto.Header{ + Height: 1234567, + Time: time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC), + }, false, log.NewNopLogger()) + + subspace := paramsKeeper.Subspace(ModuleName).WithKeyTable(types.ParamKeyTable()) + return ctx, encCfg, subspace +} diff --git a/x/globalfee/module.go b/x/globalfee/module.go new file mode 100644 index 00000000..5db2b905 --- /dev/null +++ b/x/globalfee/module.go @@ -0,0 +1,134 @@ +package globalfee + +import ( + "context" + "encoding/json" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/module" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/client/cli" + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +var ( + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleGenesis = AppModule{} + _ module.AppModule = AppModule{} +) + +// AppModuleBasic defines the basic application module used by the wasm module. +type AppModuleBasic struct{} + +func (a AppModuleBasic) Name() string { + return types.ModuleName +} + +func (a AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(&types.GenesisState{ + Params: types.DefaultParams(), + }) +} + +func (a AppModuleBasic) ValidateGenesis(marshaler codec.JSONCodec, _ client.TxEncodingConfig, message json.RawMessage) error { + var data types.GenesisState + err := marshaler.UnmarshalJSON(message, &data) + if err != nil { + return err + } + if err := data.Params.ValidateBasic(); err != nil { + return sdkerrors.Wrap(err, "params") + } + return nil +} + +func (a AppModuleBasic) RegisterInterfaces(_ codectypes.InterfaceRegistry) { +} + +func (a AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) { +} + +func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + _ = types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) +} + +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +func (a AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +func (a AppModuleBasic) RegisterLegacyAminoCodec(_ *codec.LegacyAmino) { +} + +type AppModule struct { + AppModuleBasic + paramSpace paramstypes.Subspace +} + +// NewAppModule constructor +func NewAppModule(paramSpace paramstypes.Subspace) *AppModule { + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + return &AppModule{paramSpace: paramSpace} +} + +func (a AppModule) InitGenesis(ctx sdk.Context, marshaler codec.JSONCodec, message json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + marshaler.MustUnmarshalJSON(message, &genesisState) + a.paramSpace.SetParamSet(ctx, &genesisState.Params) + return nil +} + +func (a AppModule) ExportGenesis(ctx sdk.Context, marshaler codec.JSONCodec) json.RawMessage { + var genState types.GenesisState + a.paramSpace.GetParamSet(ctx, &genState.Params) + return marshaler.MustMarshalJSON(&genState) +} + +func (a AppModule) RegisterInvariants(_ sdk.InvariantRegistry) { +} + +func (a AppModule) Route() sdk.Route { + return sdk.Route{} +} + +func (a AppModule) QuerierRoute() string { + return types.QuerierRoute +} + +func (a AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier { + return nil +} + +func (a AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterQueryServer(cfg.QueryServer(), NewGrpcQuerier(a.paramSpace)) +} + +func (a AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) { +} + +func (a AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return nil +} + +// ConsensusVersion is a sequence number for state-breaking change of the +// module. It should be incremented on each consensus-breaking change +// introduced by the module. To avoid wrong/empty versions, the initial version +// should be set to 1. +func (a AppModule) ConsensusVersion() uint64 { + return 1 +} diff --git a/x/globalfee/querier.go b/x/globalfee/querier.go new file mode 100644 index 00000000..3658d2f9 --- /dev/null +++ b/x/globalfee/querier.go @@ -0,0 +1,37 @@ +package globalfee + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +var _ types.QueryServer = &GrpcQuerier{} + +// ParamSource is a read only subset of paramtypes.Subspace +type ParamSource interface { + Get(ctx sdk.Context, key []byte, ptr interface{}) + Has(ctx sdk.Context, key []byte) bool +} + +type GrpcQuerier struct { + paramSource ParamSource +} + +func NewGrpcQuerier(paramSource ParamSource) GrpcQuerier { + return GrpcQuerier{paramSource: paramSource} +} + +// MinimumGasPrices return minimum gas prices +func (g GrpcQuerier) MinimumGasPrices(stdCtx context.Context, _ *types.QueryMinimumGasPricesRequest) (*types.QueryMinimumGasPricesResponse, error) { + var minGasPrices sdk.DecCoins + ctx := sdk.UnwrapSDKContext(stdCtx) + if g.paramSource.Has(ctx, types.ParamStoreKeyMinGasPrices) { + g.paramSource.Get(ctx, types.ParamStoreKeyMinGasPrices, &minGasPrices) + } + return &types.QueryMinimumGasPricesResponse{ + MinimumGasPrices: minGasPrices, + }, nil +} diff --git a/x/globalfee/querier_test.go b/x/globalfee/querier_test.go new file mode 100644 index 00000000..156d81af --- /dev/null +++ b/x/globalfee/querier_test.go @@ -0,0 +1,56 @@ +package globalfee + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/globalfee/types" +) + +func TestQueryMinimumGasPrices(t *testing.T) { + specs := map[string]struct { + setupStore func(ctx sdk.Context, s paramtypes.Subspace) + expMin sdk.DecCoins + }{ + "one coin": { + setupStore: func(ctx sdk.Context, s paramtypes.Subspace) { + s.SetParamSet(ctx, &types.Params{ + MinimumGasPrices: sdk.NewDecCoins(sdk.NewDecCoin("ALX", sdk.OneInt())), + }) + }, + expMin: sdk.NewDecCoins(sdk.NewDecCoin("ALX", sdk.OneInt())), + }, + "multiple coins": { + setupStore: func(ctx sdk.Context, s paramtypes.Subspace) { + s.SetParamSet(ctx, &types.Params{ + MinimumGasPrices: sdk.NewDecCoins(sdk.NewDecCoin("ALX", sdk.OneInt()), sdk.NewDecCoin("BLX", sdk.NewInt(2))), + }) + }, + expMin: sdk.NewDecCoins(sdk.NewDecCoin("ALX", sdk.OneInt()), sdk.NewDecCoin("BLX", sdk.NewInt(2))), + }, + "no min gas price set": { + setupStore: func(ctx sdk.Context, s paramtypes.Subspace) { + s.SetParamSet(ctx, &types.Params{}) + }, + }, + "no param set": { + setupStore: func(ctx sdk.Context, s paramtypes.Subspace) { + }, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx, _, subspace := setupTestStore(t) + spec.setupStore(ctx, subspace) + q := NewGrpcQuerier(subspace) + gotResp, gotErr := q.MinimumGasPrices(sdk.WrapSDKContext(ctx), nil) + require.NoError(t, gotErr) + require.NotNil(t, gotResp) + assert.Equal(t, spec.expMin, gotResp.MinimumGasPrices) + }) + } +} diff --git a/x/globalfee/types/genesis.go b/x/globalfee/types/genesis.go new file mode 100644 index 00000000..a87d8d98 --- /dev/null +++ b/x/globalfee/types/genesis.go @@ -0,0 +1,40 @@ +package types + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) *GenesisState { + return &GenesisState{ + Params: params, + } +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() *GenesisState { + return NewGenesisState(DefaultParams()) +} + +// GetGenesisStateFromAppState returns x/auth GenesisState given raw application +// genesis state. +func GetGenesisStateFromAppState(cdc codec.Codec, appState map[string]json.RawMessage) *GenesisState { + var genesisState GenesisState + + if appState[ModuleName] != nil { + cdc.MustUnmarshalJSON(appState[ModuleName], &genesisState) + } + + return &genesisState +} + +func ValidateGenesis(data GenesisState) error { + if err := data.Params.ValidateBasic(); err != nil { + return sdkerrors.Wrap(err, "globalfee params") + } + + return nil +} diff --git a/x/globalfee/types/genesis.pb.go b/x/globalfee/types/genesis.pb.go new file mode 100644 index 00000000..bde069af --- /dev/null +++ b/x/globalfee/types/genesis.pb.go @@ -0,0 +1,521 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: gaia/globalfee/v1beta1/genesis.proto + +package types + +import ( + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState - initial state of module +type GenesisState struct { + // Params of this module + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_015b3e8b7a7c65c5, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// Params defines the set of module parameters. +type Params struct { + // Minimum stores the minimum gas price(s) for all TX on the chain. + // When multiple coins are defined then they are accepted alternatively. + // The list must be sorted by denoms asc. No duplicate denoms or zero amount + // values allowed. For more information see + // https://docs.cosmos.network/main/modules/auth#concepts + MinimumGasPrices github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,1,rep,name=minimum_gas_prices,json=minimumGasPrices,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"minimum_gas_prices,omitempty" yaml:"minimum_gas_prices"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_015b3e8b7a7c65c5, []int{1} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetMinimumGasPrices() github_com_cosmos_cosmos_sdk_types.DecCoins { + if m != nil { + return m.MinimumGasPrices + } + return nil +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "gaia.globalfee.v1beta1.GenesisState") + proto.RegisterType((*Params)(nil), "gaia.globalfee.v1beta1.Params") +} + +func init() { + proto.RegisterFile("gaia/globalfee/v1beta1/genesis.proto", fileDescriptor_015b3e8b7a7c65c5) +} + +var fileDescriptor_015b3e8b7a7c65c5 = []byte{ + // 325 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x31, 0x4b, 0x03, 0x31, + 0x14, 0xc7, 0x2f, 0x08, 0x1d, 0xae, 0x0e, 0xe5, 0x10, 0xa9, 0xa5, 0xe4, 0xe4, 0x70, 0x28, 0xa8, + 0x09, 0xad, 0x9b, 0xe3, 0x29, 0x74, 0x2d, 0x75, 0x73, 0xa9, 0xb9, 0x33, 0xc6, 0x60, 0x73, 0x09, + 0x4d, 0x2a, 0xf6, 0x5b, 0xf8, 0x39, 0xfc, 0x0c, 0xee, 0x76, 0xec, 0xe8, 0x54, 0xe5, 0x6e, 0x73, + 0xf4, 0x13, 0xc8, 0x25, 0x67, 0x2b, 0x9c, 0x53, 0x02, 0xef, 0xf7, 0x7e, 0xff, 0xc7, 0x7b, 0xfe, + 0x11, 0x23, 0x9c, 0x60, 0x36, 0x95, 0x09, 0x99, 0xde, 0x51, 0x8a, 0x1f, 0xfb, 0x09, 0x35, 0xa4, + 0x8f, 0x19, 0xcd, 0xa8, 0xe6, 0x1a, 0xa9, 0x99, 0x34, 0x32, 0xd8, 0x2f, 0x29, 0xb4, 0xa1, 0x50, + 0x45, 0x75, 0xf6, 0x98, 0x64, 0xd2, 0x22, 0xb8, 0xfc, 0x39, 0xba, 0x03, 0x53, 0xa9, 0x85, 0xd4, + 0x38, 0x21, 0x7a, 0x2b, 0x4c, 0x25, 0xcf, 0x5c, 0x3d, 0xba, 0xf1, 0x77, 0x87, 0x4e, 0x7f, 0x65, + 0x88, 0xa1, 0xc1, 0xc8, 0x6f, 0x28, 0x32, 0x23, 0x42, 0xb7, 0xc1, 0x21, 0xe8, 0x35, 0x07, 0x10, + 0xfd, 0x1f, 0x87, 0x46, 0x96, 0x8a, 0xdb, 0xcb, 0x75, 0xe8, 0x7d, 0xad, 0xc3, 0x96, 0xeb, 0x3a, + 0x91, 0x82, 0x1b, 0x2a, 0x94, 0x59, 0x8c, 0x2b, 0x4f, 0xf4, 0x06, 0xfc, 0x86, 0x83, 0x83, 0x57, + 0xe0, 0x07, 0x82, 0x67, 0x5c, 0xcc, 0xc5, 0x84, 0x11, 0x3d, 0x51, 0x33, 0x9e, 0xd2, 0x32, 0x69, + 0xa7, 0xd7, 0x1c, 0x74, 0x91, 0x1b, 0x15, 0x95, 0xa3, 0x6e, 0x62, 0x2e, 0x69, 0x7a, 0x21, 0x79, + 0x16, 0xab, 0x2a, 0xa7, 0x5b, 0xef, 0xdf, 0x66, 0x7e, 0xaf, 0xc3, 0x83, 0x05, 0x11, 0xd3, 0xf3, + 0xa8, 0x4e, 0x45, 0x2f, 0x1f, 0xe1, 0x31, 0xe3, 0xe6, 0x7e, 0x9e, 0xa0, 0x54, 0x0a, 0x5c, 0xed, + 0xc5, 0x3d, 0xa7, 0xfa, 0xf6, 0x01, 0x9b, 0x85, 0xa2, 0xfa, 0x37, 0x50, 0x8f, 0x5b, 0x95, 0x63, + 0x48, 0xf4, 0xc8, 0x1a, 0xe2, 0x78, 0x99, 0x43, 0xb0, 0xca, 0x21, 0xf8, 0xcc, 0x21, 0x78, 0x2e, + 0xa0, 0xb7, 0x2a, 0xa0, 0xf7, 0x5e, 0x40, 0xef, 0xba, 0x57, 0x17, 0xdb, 0x5b, 0x3e, 0xfd, 0xb9, + 0xa6, 0xd5, 0x27, 0x0d, 0xbb, 0xf6, 0xb3, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x48, 0x6d, 0x01, + 0xcd, 0xec, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.MinimumGasPrices) > 0 { + for iNdEx := len(m.MinimumGasPrices) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MinimumGasPrices[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.MinimumGasPrices) > 0 { + for _, e := range m.MinimumGasPrices { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumGasPrices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MinimumGasPrices = append(m.MinimumGasPrices, types.DecCoin{}) + if err := m.MinimumGasPrices[len(m.MinimumGasPrices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/globalfee/types/keys.go b/x/globalfee/types/keys.go new file mode 100644 index 00000000..71b43267 --- /dev/null +++ b/x/globalfee/types/keys.go @@ -0,0 +1,8 @@ +package types + +const ( + // ModuleName is the name of the this module + ModuleName = "globalfee" + + QuerierRoute = ModuleName +) diff --git a/x/globalfee/types/params.go b/x/globalfee/types/params.go new file mode 100644 index 00000000..59aa38cc --- /dev/null +++ b/x/globalfee/types/params.go @@ -0,0 +1,97 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// ParamStoreKeyMinGasPrices store key +var ParamStoreKeyMinGasPrices = []byte("MinimumGasPricesParam") + +// DefaultParams returns default parameters +func DefaultParams() Params { + return Params{MinimumGasPrices: sdk.DecCoins{}} +} + +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ValidateBasic performs basic validation. +func (p Params) ValidateBasic() error { + return validateMinimumGasPrices(p.MinimumGasPrices) +} + +// ParamSetPairs returns the parameter set pairs. +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair( + ParamStoreKeyMinGasPrices, &p.MinimumGasPrices, validateMinimumGasPrices, + ), + } +} + +// this requires the fee non-negative +func validateMinimumGasPrices(i interface{}) error { + v, ok := i.(sdk.DecCoins) + if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "type: %T, expected sdk.DecCoins", i) + } + + dec := DecCoins(v) + return dec.Validate() +} + +// Validate checks that the DecCoins are sorted, have nonnegtive amount, with a valid and unique +// denomination (i.e no duplicates). Otherwise, it returns an error. +type DecCoins sdk.DecCoins + +func (coins DecCoins) Validate() error { + switch len(coins) { + case 0: + return nil + + case 1: + // match the denom reg expr + if err := sdk.ValidateDenom(coins[0].Denom); err != nil { + return err + } + if coins[0].IsNegative() { + return fmt.Errorf("coin %s amount is negtive", coins[0]) + } + return nil + default: + // check single coin case + if err := (DecCoins{coins[0]}).Validate(); err != nil { + return err + } + + lowDenom := coins[0].Denom + seenDenoms := make(map[string]bool) + seenDenoms[lowDenom] = true + + for _, coin := range coins[1:] { + if seenDenoms[coin.Denom] { + return fmt.Errorf("duplicate denomination %s", coin.Denom) + } + if err := sdk.ValidateDenom(coin.Denom); err != nil { + return err + } + if coin.Denom <= lowDenom { + return fmt.Errorf("denomination %s is not sorted", coin.Denom) + } + if coin.IsNegative() { + return fmt.Errorf("coin %s amount is negtive", coin.Denom) + } + + // we compare each coin against the last denom + lowDenom = coin.Denom + seenDenoms[coin.Denom] = true + } + + return nil + } +} diff --git a/x/globalfee/types/params_test.go b/x/globalfee/types/params_test.go new file mode 100644 index 00000000..b4e67379 --- /dev/null +++ b/x/globalfee/types/params_test.go @@ -0,0 +1,61 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestDefaultParams(t *testing.T) { + p := DefaultParams() + require.EqualValues(t, p.MinimumGasPrices, sdk.DecCoins{}) +} + +func Test_validateParams(t *testing.T) { + tests := map[string]struct { + coins interface{} // not sdk.DeCoins, but Decoins defined in glboalfee + expectErr bool + }{ + "DefaultParams, pass": { + DefaultParams().MinimumGasPrices, + false, + }, + "DecCoins conversion fails, fail": { + sdk.Coins{sdk.NewCoin("photon", sdk.OneInt())}, + true, + }, + "coins amounts are zero, pass": { + sdk.DecCoins{ + sdk.NewDecCoin("atom", sdk.ZeroInt()), + sdk.NewDecCoin("photon", sdk.ZeroInt()), + }, + false, + }, + "duplicate coins denoms, fail": { + sdk.DecCoins{ + sdk.NewDecCoin("photon", sdk.OneInt()), + sdk.NewDecCoin("photon", sdk.OneInt()), + }, + true, + }, + "coins are not sorted by denom alphabetically, fail": { + sdk.DecCoins{ + sdk.NewDecCoin("photon", sdk.OneInt()), + sdk.NewDecCoin("atom", sdk.OneInt()), + }, + true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + err := validateMinimumGasPrices(test.coins) + if test.expectErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/x/globalfee/types/query.pb.go b/x/globalfee/types/query.pb.go new file mode 100644 index 00000000..569b6975 --- /dev/null +++ b/x/globalfee/types/query.pb.go @@ -0,0 +1,553 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: gaia/globalfee/v1beta1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryMinimumGasPricesRequest is the request type for the +// Query/MinimumGasPrices RPC method. +type QueryMinimumGasPricesRequest struct { +} + +func (m *QueryMinimumGasPricesRequest) Reset() { *m = QueryMinimumGasPricesRequest{} } +func (m *QueryMinimumGasPricesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryMinimumGasPricesRequest) ProtoMessage() {} +func (*QueryMinimumGasPricesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_12a736cede25d10a, []int{0} +} +func (m *QueryMinimumGasPricesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMinimumGasPricesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMinimumGasPricesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMinimumGasPricesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMinimumGasPricesRequest.Merge(m, src) +} +func (m *QueryMinimumGasPricesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryMinimumGasPricesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMinimumGasPricesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMinimumGasPricesRequest proto.InternalMessageInfo + +// QueryMinimumGasPricesResponse is the response type for the +// Query/MinimumGasPrices RPC method. +type QueryMinimumGasPricesResponse struct { + MinimumGasPrices github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,1,rep,name=minimum_gas_prices,json=minimumGasPrices,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"minimum_gas_prices,omitempty" yaml:"minimum_gas_prices"` +} + +func (m *QueryMinimumGasPricesResponse) Reset() { *m = QueryMinimumGasPricesResponse{} } +func (m *QueryMinimumGasPricesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryMinimumGasPricesResponse) ProtoMessage() {} +func (*QueryMinimumGasPricesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_12a736cede25d10a, []int{1} +} +func (m *QueryMinimumGasPricesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMinimumGasPricesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMinimumGasPricesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMinimumGasPricesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMinimumGasPricesResponse.Merge(m, src) +} +func (m *QueryMinimumGasPricesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryMinimumGasPricesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMinimumGasPricesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMinimumGasPricesResponse proto.InternalMessageInfo + +func (m *QueryMinimumGasPricesResponse) GetMinimumGasPrices() github_com_cosmos_cosmos_sdk_types.DecCoins { + if m != nil { + return m.MinimumGasPrices + } + return nil +} + +func init() { + proto.RegisterType((*QueryMinimumGasPricesRequest)(nil), "gaia.globalfee.v1beta1.QueryMinimumGasPricesRequest") + proto.RegisterType((*QueryMinimumGasPricesResponse)(nil), "gaia.globalfee.v1beta1.QueryMinimumGasPricesResponse") +} + +func init() { + proto.RegisterFile("gaia/globalfee/v1beta1/query.proto", fileDescriptor_12a736cede25d10a) +} + +var fileDescriptor_12a736cede25d10a = []byte{ + // 377 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xb1, 0x4f, 0xdb, 0x40, + 0x14, 0xc6, 0x7d, 0xad, 0xda, 0xc1, 0x5d, 0x22, 0xab, 0xaa, 0xda, 0xc8, 0x3d, 0x57, 0x9e, 0xa2, + 0x36, 0xbd, 0x53, 0xd2, 0x76, 0xe9, 0x98, 0x56, 0x62, 0x42, 0x82, 0x8c, 0x2c, 0xd1, 0xd9, 0x1c, + 0xc7, 0x09, 0x9f, 0x9f, 0x93, 0x3b, 0x23, 0xbc, 0xf2, 0x17, 0x20, 0xf1, 0x5f, 0xb0, 0xb2, 0xc2, + 0x9e, 0x31, 0x12, 0x0b, 0x93, 0x41, 0x09, 0x13, 0x23, 0x7f, 0x01, 0xb2, 0x9d, 0x00, 0x8a, 0x09, + 0x12, 0x93, 0x2d, 0x7d, 0xbf, 0xf7, 0x3e, 0x7d, 0xdf, 0x3d, 0xdb, 0x17, 0x4c, 0x32, 0x2a, 0x22, + 0x08, 0x58, 0xb4, 0xc3, 0x39, 0xdd, 0xef, 0x04, 0xdc, 0xb0, 0x0e, 0x1d, 0xa6, 0x7c, 0x94, 0x91, + 0x64, 0x04, 0x06, 0x9c, 0x4f, 0x05, 0x43, 0x1e, 0x18, 0x32, 0x67, 0x9a, 0x1f, 0x05, 0x08, 0x28, + 0x11, 0x5a, 0xfc, 0x55, 0x74, 0xd3, 0x15, 0x00, 0x22, 0xe2, 0x94, 0x25, 0x92, 0xb2, 0x38, 0x06, + 0xc3, 0x8c, 0x84, 0x58, 0xcf, 0x55, 0x1c, 0x82, 0x56, 0xa0, 0x69, 0xc0, 0xf4, 0xa3, 0x59, 0x08, + 0x32, 0xae, 0x74, 0x1f, 0xdb, 0xee, 0x66, 0x61, 0xbd, 0x2e, 0x63, 0xa9, 0x52, 0xb5, 0xc6, 0xf4, + 0xc6, 0x48, 0x86, 0x5c, 0xf7, 0xf9, 0x30, 0xe5, 0xda, 0xf8, 0x39, 0xb2, 0xbf, 0xae, 0x00, 0x74, + 0x02, 0xb1, 0xe6, 0xce, 0x19, 0xb2, 0x1d, 0x55, 0x89, 0x03, 0xc1, 0xf4, 0x20, 0x29, 0xe5, 0xcf, + 0xe8, 0xdb, 0xdb, 0xd6, 0x87, 0xae, 0x4b, 0x2a, 0x7f, 0x52, 0xf8, 0x2f, 0x82, 0x90, 0xff, 0x3c, + 0xfc, 0x07, 0x32, 0xee, 0x25, 0xe3, 0xdc, 0xb3, 0x6e, 0x73, 0xcf, 0xad, 0xcf, 0xb7, 0x41, 0x49, + 0xc3, 0x55, 0x62, 0xb2, 0xbb, 0xdc, 0xfb, 0x92, 0x31, 0x15, 0xfd, 0xf5, 0xeb, 0x94, 0x7f, 0x72, + 0xe5, 0xfd, 0x10, 0xd2, 0xec, 0xa6, 0x01, 0x09, 0x41, 0xd1, 0x79, 0xd8, 0xea, 0xf3, 0x53, 0x6f, + 0xef, 0x51, 0x93, 0x25, 0x5c, 0x2f, 0x0c, 0x75, 0xbf, 0xa1, 0x96, 0x62, 0x74, 0xcf, 0x91, 0xfd, + 0xae, 0x0c, 0xe8, 0x9c, 0x22, 0xbb, 0xb1, 0x9c, 0xd2, 0xf9, 0x4d, 0x9e, 0x7f, 0x0c, 0xf2, 0x52, + 0x6b, 0xcd, 0x3f, 0xaf, 0x9c, 0xaa, 0xaa, 0xf4, 0xbb, 0x87, 0x17, 0x37, 0xc7, 0x6f, 0xda, 0xce, + 0x77, 0xba, 0xe2, 0x4a, 0xea, 0x0d, 0xf4, 0x7a, 0xe3, 0x29, 0x46, 0x93, 0x29, 0x46, 0xd7, 0x53, + 0x8c, 0x8e, 0x66, 0xd8, 0x9a, 0xcc, 0xb0, 0x75, 0x39, 0xc3, 0xd6, 0x56, 0xab, 0x5e, 0x4c, 0xb9, + 0xf6, 0xe0, 0xc9, 0xe2, 0xb2, 0x9e, 0xe0, 0x7d, 0x79, 0x0b, 0xbf, 0xee, 0x03, 0x00, 0x00, 0xff, + 0xff, 0x73, 0x0d, 0xf8, 0x43, 0x9d, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + MinimumGasPrices(ctx context.Context, in *QueryMinimumGasPricesRequest, opts ...grpc.CallOption) (*QueryMinimumGasPricesResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) MinimumGasPrices(ctx context.Context, in *QueryMinimumGasPricesRequest, opts ...grpc.CallOption) (*QueryMinimumGasPricesResponse, error) { + out := new(QueryMinimumGasPricesResponse) + err := c.cc.Invoke(ctx, "/gaia.globalfee.v1beta1.Query/MinimumGasPrices", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + MinimumGasPrices(context.Context, *QueryMinimumGasPricesRequest) (*QueryMinimumGasPricesResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) MinimumGasPrices(ctx context.Context, req *QueryMinimumGasPricesRequest) (*QueryMinimumGasPricesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MinimumGasPrices not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_MinimumGasPrices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryMinimumGasPricesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).MinimumGasPrices(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gaia.globalfee.v1beta1.Query/MinimumGasPrices", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).MinimumGasPrices(ctx, req.(*QueryMinimumGasPricesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "gaia.globalfee.v1beta1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "MinimumGasPrices", + Handler: _Query_MinimumGasPrices_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "gaia/globalfee/v1beta1/query.proto", +} + +func (m *QueryMinimumGasPricesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMinimumGasPricesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMinimumGasPricesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryMinimumGasPricesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMinimumGasPricesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMinimumGasPricesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.MinimumGasPrices) > 0 { + for iNdEx := len(m.MinimumGasPrices) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MinimumGasPrices[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryMinimumGasPricesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryMinimumGasPricesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.MinimumGasPrices) > 0 { + for _, e := range m.MinimumGasPrices { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryMinimumGasPricesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMinimumGasPricesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMinimumGasPricesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMinimumGasPricesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMinimumGasPricesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMinimumGasPricesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumGasPrices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MinimumGasPrices = append(m.MinimumGasPrices, types.DecCoin{}) + if err := m.MinimumGasPrices[len(m.MinimumGasPrices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/globalfee/types/query.pb.gw.go b/x/globalfee/types/query.pb.gw.go new file mode 100644 index 00000000..0e33d9df --- /dev/null +++ b/x/globalfee/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: gaia/globalfee/v1beta1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_MinimumGasPrices_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMinimumGasPricesRequest + var metadata runtime.ServerMetadata + + msg, err := client.MinimumGasPrices(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_MinimumGasPrices_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMinimumGasPricesRequest + var metadata runtime.ServerMetadata + + msg, err := server.MinimumGasPrices(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_MinimumGasPrices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_MinimumGasPrices_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MinimumGasPrices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_MinimumGasPrices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_MinimumGasPrices_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MinimumGasPrices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_MinimumGasPrices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"gaia", "globalfee", "v1beta1", "minimum_gas_prices"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_Query_MinimumGasPrices_0 = runtime.ForwardResponseMessage +)