From 23bf19efb0c75381f10180887a9f1e708dd40434 Mon Sep 17 00:00:00 2001 From: Nick DeLuca Date: Fri, 20 May 2022 17:12:56 -0700 Subject: [PATCH] Filter out the evm denom from min gas prices in the cosmos-sdk ante (#1253) * Filter out the evm denom from min gas prices in the cosmos-sdk ante chain; this allows '0ukava;1000000000akava' to be used in order to still support zero fees for cosmos based transactions * support called context in mock * ensure min gas prices is always a valid (and sorted) DecCoins * add doc comments --- app/ante/ante.go | 1 + app/ante/authorized_test.go | 2 + app/ante/min_gas_filter.go | 44 +++++++++++++++++ app/ante/min_gas_filter_test.go | 88 +++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 app/ante/min_gas_filter.go create mode 100644 app/ante/min_gas_filter_test.go diff --git a/app/ante/ante.go b/app/ante/ante.go index 9b87061f..cd435cab 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -103,6 +103,7 @@ func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { decorators = append(decorators, NewAuthenticatedMempoolDecorator(options.AddressFetchers...)) } decorators = append(decorators, + NewEvmMinGasFilter(options.EvmKeeper), // filter out evm denom from min-gas-prices authante.NewMempoolFeeDecorator(), NewVestingAccountDecorator(), NewAuthzLimiterDecorator( diff --git a/app/ante/authorized_test.go b/app/ante/authorized_test.go index f962f2b1..d9bb976c 100644 --- a/app/ante/authorized_test.go +++ b/app/ante/authorized_test.go @@ -16,10 +16,12 @@ var _ sdk.AnteHandler = (&MockAnteHandler{}).AnteHandle type MockAnteHandler struct { WasCalled bool + CalledCtx sdk.Context } func (mah *MockAnteHandler) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { mah.WasCalled = true + mah.CalledCtx = ctx return ctx, nil } diff --git a/app/ante/min_gas_filter.go b/app/ante/min_gas_filter.go new file mode 100644 index 00000000..ebfe4d59 --- /dev/null +++ b/app/ante/min_gas_filter.go @@ -0,0 +1,44 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +var _ sdk.AnteDecorator = EvmMinGasFilter{} + +// EVMKeeper specifies the interface that EvmMinGasFilter requires +type EVMKeeper interface { + GetParams(ctx sdk.Context) evmtypes.Params +} + +// EvmMinGasFilter filters out the EvmDenom min gas price and calls the next ante handle with an updated context +type EvmMinGasFilter struct { + evmKeeper EVMKeeper +} + +// NewEvmMinGasFilter takes an EVMKeeper and returns a new min gas filter for it's EvmDenom +func NewEvmMinGasFilter(evmKeeper EVMKeeper) EvmMinGasFilter { + return EvmMinGasFilter{ + evmKeeper: evmKeeper, + } +} + +// AnteHandle checks the EvmDenom from the evmKeeper and filters out the EvmDenom from the ctx +func (emgf EvmMinGasFilter) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + evmDenom := emgf.evmKeeper.GetParams(ctx).EvmDenom + + if ctx.MinGasPrices().AmountOf(evmDenom).IsPositive() { + filteredMinGasPrices := sdk.NewDecCoins() + + for _, gasPrice := range ctx.MinGasPrices() { + if gasPrice.Denom != evmDenom { + filteredMinGasPrices = filteredMinGasPrices.Add(gasPrice) + } + } + + ctx = ctx.WithMinGasPrices(filteredMinGasPrices) + } + + return next(ctx, tx, simulate) +} diff --git a/app/ante/min_gas_filter_test.go b/app/ante/min_gas_filter_test.go new file mode 100644 index 00000000..9314049b --- /dev/null +++ b/app/ante/min_gas_filter_test.go @@ -0,0 +1,88 @@ +package ante_test + +import ( + "strings" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + evmtypes "github.com/tharsis/ethermint/x/evm/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/app/ante" +) + +func mustParseDecCoins(value string) sdk.DecCoins { + coins, err := sdk.ParseDecCoins(strings.ReplaceAll(value, ";", ",")) + if err != nil { + panic(err) + } + + return coins +} + +func TestEvmMinGasFilter(t *testing.T) { + tApp := app.NewTestApp() + handler := ante.NewEvmMinGasFilter(tApp.GetEvmKeeper()) + + ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) + tApp.GetEvmKeeper().SetParams(ctx, evmtypes.Params{ + EvmDenom: "akava", + }) + + testCases := []struct { + name string + minGasPrices sdk.DecCoins + expectedMinGasPrices sdk.DecCoins + }{ + { + "no min gas prices", + mustParseDecCoins(""), + mustParseDecCoins(""), + }, + { + "zero ukava gas price", + mustParseDecCoins("0ukava"), + mustParseDecCoins("0ukava"), + }, + { + "non-zero ukava gas price", + mustParseDecCoins("0.001ukava"), + mustParseDecCoins("0.001ukava"), + }, + { + "zero ukava gas price, min akava price", + mustParseDecCoins("0ukava;100000akava"), + mustParseDecCoins("0ukava"), // akava is removed + }, + { + "zero ukava gas price, min akava price, other token", + mustParseDecCoins("0ukava;100000akava;0.001other"), + mustParseDecCoins("0ukava;0.001other"), // akava is removed + }, + { + "non-zero ukava gas price, min akava price", + mustParseDecCoins("0.25ukava;100000akava;0.001other"), + mustParseDecCoins("0.25ukava;0.001other"), // akava is removed + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) + + ctx = ctx.WithMinGasPrices(tc.minGasPrices) + mmd := MockAnteHandler{} + + _, err := handler.AnteHandle(ctx, nil, false, mmd.AnteHandle) + require.NoError(t, err) + require.True(t, mmd.WasCalled) + + assert.NoError(t, mmd.CalledCtx.MinGasPrices().Validate()) + assert.Equal(t, tc.expectedMinGasPrices, mmd.CalledCtx.MinGasPrices()) + }) + } +}