From 046cd001749c04183d6987c7baedf2788f9e039a Mon Sep 17 00:00:00 2001 From: Loredana Cirstea Date: Tue, 31 May 2022 19:28:46 +0300 Subject: [PATCH] feemarket: global `MinGasPrice` parameter (#1104) * Add min_gas_price to feemarket params * Add MinGasPriceDecorators * feemarket integration tests for MinGasPrice * Restructure integration tests * Simplify integration tests context We use DeliverTx context to set up the app, otherwise not all settings are initialized. We test CheckTx with `s.app.BaseApp.CheckTx(req)`, which uses the `CheckTx` mode and context. * Update MinGasPrice spec in feemarket module * reorder ethermint module order for initializing genesis * feemarket migrations for adding MinGasPrice param * update changelog * Additional unit tests for MinGasPrice = 0, tx gas price > 0 (PR review) https://github.com/tharsis/ethermint/pull/1104#discussion_r884991661 * Use 0 MinGasPrice for transaction simulations * Fix duplicate registration of feemarket GenesisState and Params (PR review) https://github.com/tharsis/ethermint/pull/1104#issuecomment-1141893712 --- CHANGELOG.md | 1 + app/ante/fees.go | 114 +++ app/ante/fees_test.go | 339 +++++++++ app/ante/handler_options.go | 7 +- app/ante/interfaces.go | 6 + app/ante/utils_test.go | 45 ++ app/app.go | 9 +- docs/api/proto-docs.md | 1 + go.mod | 5 +- go.sum | 19 +- proto/ethermint/feemarket/v1/feemarket.proto | 7 +- tests/e2e/integration_test.go | 2 +- testutil/fund.go | 29 + tools/tools.go | 2 +- x/feemarket/keeper/integration_test.go | 655 ++++++++++++++++++ x/feemarket/keeper/keeper_test.go | 58 +- x/feemarket/keeper/migrations.go | 6 + x/feemarket/migrations/v010/migrate.go | 2 +- .../migrations/v010/types/feemarket.pb.go | 472 +++++++++++++ .../migrations/v010/types/genesis.pb.go | 356 ++++++++++ x/feemarket/migrations/v010/types/params.go | 134 ++++ x/feemarket/migrations/v011/migrate.go | 37 + x/feemarket/migrations/v011/migrate_test.go | 73 ++ x/feemarket/module.go | 7 +- x/feemarket/simulation/genesis.go | 4 +- x/feemarket/spec/01_concepts.md | 3 + x/feemarket/spec/07_params.md | 3 +- x/feemarket/spec/09_antehandlers.md | 25 + ...rovements.md => 10_future_improvements.md} | 0 x/feemarket/types/feemarket.pb.go | 96 ++- x/feemarket/types/params.go | 33 +- x/feemarket/types/params_test.go | 39 +- 32 files changed, 2534 insertions(+), 55 deletions(-) create mode 100644 app/ante/fees.go create mode 100644 app/ante/fees_test.go create mode 100644 testutil/fund.go create mode 100644 x/feemarket/keeper/integration_test.go create mode 100644 x/feemarket/migrations/v010/types/feemarket.pb.go create mode 100644 x/feemarket/migrations/v010/types/genesis.pb.go create mode 100644 x/feemarket/migrations/v010/types/params.go create mode 100644 x/feemarket/migrations/v011/migrate.go create mode 100644 x/feemarket/migrations/v011/migrate_test.go create mode 100644 x/feemarket/spec/09_antehandlers.md rename x/feemarket/spec/{09_future_improvements.md => 10_future_improvements.md} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c25e7294..1027a1f08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking +- (feemarket) [tharsis#1104](https://github.com/tharsis/ethermint/pull/1104) Enforce a minimum gas price for Cosmos and EVM transactions through the `MinGasPrice` parameter. - (rpc) [tharsis#1081](https://github.com/tharsis/ethermint/pull/1081) Deduplicate some json-rpc logic codes, cleanup several dead functions. - (ante) [tharsis#1062](https://github.com/tharsis/ethermint/pull/1062) Emit event of eth tx hash in ante handler to support query failed transactions. diff --git a/app/ante/fees.go b/app/ante/fees.go new file mode 100644 index 0000000000..ae94101bad --- /dev/null +++ b/app/ante/fees.go @@ -0,0 +1,114 @@ +package ante + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +// MinGasPriceDecorator will check if the transaction's fee is at least as large +// as the MinGasPrices param. If fee is too low, decorator returns error and tx +// is rejected. This applies for both CheckTx and DeliverTx +// If fee is high enough, then call next AnteHandler +// CONTRACT: Tx must implement FeeTx to use MinGasPriceDecorator +type MinGasPriceDecorator struct { + feesKeeper FeeMarketKeeper + evmKeeper EVMKeeper +} + +func NewMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) MinGasPriceDecorator { + return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} +} + +func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + minGasPrice := mpd.feesKeeper.GetParams(ctx).MinGasPrice + minGasPrices := sdk.DecCoins{sdk.DecCoin{ + Denom: mpd.evmKeeper.GetParams(ctx).EvmDenom, + Amount: minGasPrice, + }} + + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + feeCoins := feeTx.GetFee() + gas := feeTx.GetGas() + + if !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + gasLimit := sdk.NewDec(int64(gas)) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(gasLimit) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + if !feeCoins.IsAnyGTE(requiredFees) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "provided fee < minimum global fee (%s < %s). Please increase the gas price.", feeCoins, requiredFees) + } + } + + return next(ctx, tx, simulate) +} + +// EthMinGasPriceDecorator will check if the transaction's fee is at least as large +// as the MinGasPrices param. If fee is too low, decorator returns error and tx +// is rejected. This applies to both CheckTx and DeliverTx and regardless +// if London hard fork or fee market params (EIP-1559) are enabled. +// If fee is high enough, then call next AnteHandler +type EthMinGasPriceDecorator struct { + feesKeeper FeeMarketKeeper + evmKeeper EVMKeeper +} + +func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator { + return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} +} + +func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + minGasPrice := empd.feesKeeper.GetParams(ctx).MinGasPrice + + if !minGasPrice.IsZero() { + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + + feeAmt := ethMsg.GetFee() + + // For dynamic transactions, GetFee() uses the GasFeeCap value, which + // is the maximum gas price that the signer can pay. In practice, the + // signer can pay less, if the block's BaseFee is lower. So, in this case, + // we use the EffectiveFee. If the feemarket formula results in a BaseFee + // that lowers EffectivePrice until it is < MinGasPrices, the users must + // increase the GasTipCap (priority fee) until EffectivePrice > MinGasPrices. + // Transactions with MinGasPrices * gasUsed < tx fees < EffectiveFee are rejected + // by the feemarket AnteHandle + txData, err := evmtypes.UnpackTxData(ethMsg.Data) + if err != nil { + return ctx, sdkerrors.Wrapf(err, "failed to unpack tx data %s", ethMsg.Hash) + } + if txData.TxType() != ethtypes.LegacyTxType { + paramsEvm := empd.evmKeeper.GetParams(ctx) + ethCfg := paramsEvm.ChainConfig.EthereumConfig(empd.evmKeeper.ChainID()) + baseFee := empd.evmKeeper.GetBaseFee(ctx, ethCfg) + feeAmt = ethMsg.GetEffectiveFee(baseFee) + } + + gasLimit := sdk.NewDec(int64(ethMsg.GetGas())) + requiredFee := minGasPrice.Mul(gasLimit) + + if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "provided fee < minimum global fee (%s < %s). Please increase the priority tip (for EIP-1559 txs) or the gas prices (for access list or legacy txs)", feeAmt, requiredFee) + } + } + } + + return next(ctx, tx, simulate) +} diff --git a/app/ante/fees_test.go b/app/ante/fees_test.go new file mode 100644 index 0000000000..a0d5405017 --- /dev/null +++ b/app/ante/fees_test.go @@ -0,0 +1,339 @@ +package ante_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/tharsis/ethermint/app/ante" + "github.com/tharsis/ethermint/tests" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +var execTypes = []struct { + name string + isCheckTx bool + simulate bool +}{ + {"deliverTx", false, false}, + {"deliverTxSimulate", false, true}, +} + +func (s AnteTestSuite) TestMinGasPriceDecorator() { + denom := evmtypes.DefaultEVMDenom + testMsg := banktypes.MsgSend{ + FromAddress: "evmos1x8fhpj9nmhqk8z9kpgjt95ck2xwyue0ptzkucp", + ToAddress: "evmos1dx67l23hz9l0k9hcher8xz04uj7wf3yu26l2yn", + Amount: sdk.Coins{sdk.Coin{Amount: sdk.NewInt(10), Denom: denom}}, + } + + testCases := []struct { + name string + malleate func() sdk.Tx + expPass bool + errMsg string + }{ + { + "invalid cosmos tx type", + func() sdk.Tx { + return &invalidTx{} + }, + false, + "must be a FeeTx", + }, + { + "valid cosmos tx with MinGasPrices = 0, gasPrice = 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + txBuilder := s.CreateTestCosmosTxBuilder(sdk.NewInt(0), denom, &testMsg) + return txBuilder.GetTx() + }, + true, + "", + }, + { + "valid cosmos tx with MinGasPrices = 0, gasPrice > 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + txBuilder := s.CreateTestCosmosTxBuilder(sdk.NewInt(10), denom, &testMsg) + return txBuilder.GetTx() + }, + true, + "", + }, + { + "valid cosmos tx with MinGasPrices = 10, gasPrice = 10", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + txBuilder := s.CreateTestCosmosTxBuilder(sdk.NewInt(10), denom, &testMsg) + return txBuilder.GetTx() + }, + true, + "", + }, + { + "invalid cosmos tx with MinGasPrices = 10, gasPrice = 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + txBuilder := s.CreateTestCosmosTxBuilder(sdk.NewInt(0), denom, &testMsg) + return txBuilder.GetTx() + }, + false, + "provided fee < minimum global fee", + }, + { + "invalid cosmos tx with wrong denom", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + txBuilder := s.CreateTestCosmosTxBuilder(sdk.NewInt(10), "stake", &testMsg) + return txBuilder.GetTx() + }, + false, + "provided fee < minimum global fee", + }, + } + + for _, et := range execTypes { + for _, tc := range testCases { + s.Run(et.name+"_"+tc.name, func() { + // s.SetupTest(et.isCheckTx) + ctx := s.ctx.WithIsReCheckTx(et.isCheckTx) + dec := ante.NewMinGasPriceDecorator(s.app.FeeMarketKeeper, s.app.EvmKeeper) + _, err := dec.AnteHandle(ctx, tc.malleate(), et.simulate, nextFn) + + if tc.expPass { + s.Require().NoError(err, tc.name) + } else { + s.Require().Error(err, tc.name) + s.Require().Contains(err.Error(), tc.errMsg, tc.name) + } + }) + } + } +} + +func (s AnteTestSuite) TestEthMinGasPriceDecorator() { + denom := evmtypes.DefaultEVMDenom + from, privKey := tests.NewAddrKey() + to := tests.GenerateAddress() + emptyAccessList := ethtypes.AccessList{} + + testCases := []struct { + name string + malleate func() sdk.Tx + expPass bool + errMsg string + }{ + { + "invalid tx type", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + return &invalidTx{} + }, + false, + "invalid message type", + }, + { + "wrong tx type", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + testMsg := banktypes.MsgSend{ + FromAddress: "evmos1x8fhpj9nmhqk8z9kpgjt95ck2xwyue0ptzkucp", + ToAddress: "evmos1dx67l23hz9l0k9hcher8xz04uj7wf3yu26l2yn", + Amount: sdk.Coins{sdk.Coin{Amount: sdk.NewInt(10), Denom: denom}}, + } + txBuilder := s.CreateTestCosmosTxBuilder(sdk.NewInt(0), denom, &testMsg) + return txBuilder.GetTx() + }, + false, + "invalid message type", + }, + { + "valid: invalid tx type with MinGasPrices = 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + return &invalidTx{} + }, + true, + "", + }, + { + "valid legacy tx with MinGasPrices = 0, gasPrice = 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(0), nil, nil, nil) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + { + "valid legacy tx with MinGasPrices = 0, gasPrice > 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(10), nil, nil, nil) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + { + "valid legacy tx with MinGasPrices = 10, gasPrice = 10", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(10), nil, nil, nil) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + { + "invalid legacy tx with MinGasPrices = 10, gasPrice = 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(0), nil, nil, nil) + return s.CreateTestTx(msg, privKey, 1, false) + }, + false, + "provided fee < minimum global fee", + }, + { + "valid dynamic tx with MinGasPrices = 0, EffectivePrice = 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(0), big.NewInt(0), &emptyAccessList) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + { + "valid dynamic tx with MinGasPrices = 0, EffectivePrice > 0", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.ZeroDec() + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(100), big.NewInt(50), &emptyAccessList) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + { + "valid dynamic tx with MinGasPrices < EffectivePrice", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(100), big.NewInt(100), &emptyAccessList) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + { + "invalid dynamic tx with MinGasPrices > EffectivePrice", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(0), big.NewInt(0), &emptyAccessList) + return s.CreateTestTx(msg, privKey, 1, false) + }, + false, + "provided fee < minimum global fee", + }, + { + "invalid dynamic tx with MinGasPrices > BaseFee, MinGasPrices > EffectivePrice", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(100) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + feemarketParams := s.app.FeeMarketKeeper.GetParams(s.ctx) + feemarketParams.BaseFee = sdk.NewInt(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, feemarketParams) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(1000), big.NewInt(0), &emptyAccessList) + return s.CreateTestTx(msg, privKey, 1, false) + }, + false, + "provided fee < minimum global fee", + }, + { + "valid dynamic tx with MinGasPrices > BaseFee, MinGasPrices < EffectivePrice (big GasTipCap)", + func() sdk.Tx { + params := s.app.FeeMarketKeeper.GetParams(s.ctx) + params.MinGasPrice = sdk.NewDec(100) + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + + feemarketParams := s.app.FeeMarketKeeper.GetParams(s.ctx) + feemarketParams.BaseFee = sdk.NewInt(10) + s.app.FeeMarketKeeper.SetParams(s.ctx, feemarketParams) + + msg := s.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(1000), big.NewInt(101), &emptyAccessList) + return s.CreateTestTx(msg, privKey, 1, false) + }, + true, + "", + }, + } + + for _, et := range execTypes { + for _, tc := range testCases { + s.Run(et.name+"_"+tc.name, func() { + // s.SetupTest(et.isCheckTx) + s.SetupTest() + dec := ante.NewEthMinGasPriceDecorator(s.app.FeeMarketKeeper, s.app.EvmKeeper) + _, err := dec.AnteHandle(s.ctx, tc.malleate(), et.simulate, nextFn) + + if tc.expPass { + s.Require().NoError(err, tc.name) + } else { + s.Require().Error(err, tc.name) + s.Require().Contains(err.Error(), tc.errMsg, tc.name) + } + }) + } + } +} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 61b5f4e38d..b9b5562250 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -49,8 +49,9 @@ func (options HandlerOptions) Validate() error { func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { return sdk.ChainAnteDecorators( - NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first - NewEthMempoolFeeDecorator(options.EvmKeeper), // Check eth effective gas price against minimal-gas-prices + NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first + NewEthMempoolFeeDecorator(options.EvmKeeper), // Check eth effective gas price against minimal-gas-prices + NewEthMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), // Check eth effective gas price against the global MinGasPrice NewEthValidateBasicDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.EvmKeeper), @@ -67,6 +68,7 @@ func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { ante.NewSetUpContextDecorator(), ante.NewRejectExtensionOptionsDecorator(), ante.NewMempoolFeeDecorator(), + NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), ante.NewValidateBasicDecorator(), ante.NewTxTimeoutHeightDecorator(), ante.NewValidateMemoDecorator(options.AccountKeeper), @@ -89,6 +91,7 @@ func newCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler { // NOTE: extensions option decorator removed // ante.NewRejectExtensionOptionsDecorator(), ante.NewMempoolFeeDecorator(), + NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), ante.NewValidateBasicDecorator(), ante.NewTxTimeoutHeightDecorator(), ante.NewValidateMemoDecorator(options.AccountKeeper), diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index 68478dbf20..3fb8b8b75a 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/tharsis/ethermint/x/evm/statedb" evmtypes "github.com/tharsis/ethermint/x/evm/types" + feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" ) // EVMKeeper defines the expected keeper interface used on the Eth AnteHandler @@ -32,3 +33,8 @@ type EVMKeeper interface { type protoTxProvider interface { GetProtoTx() *tx.Tx } + +// FeeMarketKeeper defines the expected keeper interface used on the AnteHandler +type FeeMarketKeeper interface { + GetParams(ctx sdk.Context) (params feemarkettypes.Params) +} diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 83f30378ea..f6e51c4936 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -2,6 +2,7 @@ package ante_test import ( "math" + "math/big" "testing" "time" @@ -123,6 +124,38 @@ func TestAnteTestSuite(t *testing.T) { }) } +func (s *AnteTestSuite) BuildTestEthTx( + from common.Address, + to common.Address, + amount *big.Int, + input []byte, + gasPrice *big.Int, + gasFeeCap *big.Int, + gasTipCap *big.Int, + accesses *ethtypes.AccessList, +) *evmtypes.MsgEthereumTx { + chainID := s.app.EvmKeeper.ChainID() + nonce := s.app.EvmKeeper.GetNonce( + s.ctx, + common.BytesToAddress(from.Bytes()), + ) + gasLimit := uint64(100000) + msgEthereumTx := evmtypes.NewTx( + chainID, + nonce, + &to, + amount, + gasLimit, + gasPrice, + gasFeeCap, + gasTipCap, + input, + accesses, + ) + msgEthereumTx.From = from.String() + return msgEthereumTx +} + // CreateTestTx is a helper function to create a tx given multiple inputs. func (suite *AnteTestSuite) CreateTestTx( msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool, @@ -203,6 +236,18 @@ func (suite *AnteTestSuite) CreateTestTxBuilder( return txBuilder } +func (suite *AnteTestSuite) CreateTestCosmosTxBuilder(gasPrice sdk.Int, denom string, msgs ...sdk.Msg) client.TxBuilder { + gasLimit := uint64(1000000) + txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() + + txBuilder.SetGasLimit(gasLimit) + fees := &sdk.Coins{{Denom: denom, Amount: gasPrice.MulRaw(int64(gasLimit))}} + txBuilder.SetFeeAmount(*fees) + err := txBuilder.SetMsgs(msgs...) + suite.Require().NoError(err) + return txBuilder +} + func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { // Build MsgSend recipient := sdk.AccAddress(common.Address{}.Bytes()) diff --git a/app/app.go b/app/app.go index 43120044a4..adf069f4cf 100644 --- a/app/app.go +++ b/app/app.go @@ -507,6 +507,11 @@ func NewEthermintApp( govtypes.ModuleName, minttypes.ModuleName, ibchost.ModuleName, + // evm module denomination is used by the feemarket module, in AnteHandle + evmtypes.ModuleName, + // NOTE: feemarket need to be initialized before genutil module: + // gentx transactions use MinGasPriceDecorator.AnteHandle + feemarkettypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, ibctransfertypes.ModuleName, @@ -515,10 +520,6 @@ func NewEthermintApp( paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, - // Ethermint modules - evmtypes.ModuleName, - feemarkettypes.ModuleName, - // NOTE: crisis module must go at the end to check for invariants on each module crisistypes.ModuleName, ) diff --git a/docs/api/proto-docs.md b/docs/api/proto-docs.md index 3ccc66e8cd..61ea93a191 100644 --- a/docs/api/proto-docs.md +++ b/docs/api/proto-docs.md @@ -954,6 +954,7 @@ Params defines the EVM module parameters | `elasticity_multiplier` | [uint32](#uint32) | | elasticity multiplier bounds the maximum gas limit an EIP-1559 block may have. | | `enable_height` | [int64](#int64) | | height at which the base fee calculation is enabled. | | `base_fee` | [string](#string) | | base fee for EIP-1559 blocks. | +| `min_gas_price` | [string](#string) | | min_gas_price defines the minimum gas price value for cosmos and eth transactions | diff --git a/go.mod b/go.mod index 79d7236d0f..f3ac097bed 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,8 @@ require ( github.com/holiman/uint256 v1.2.0 github.com/improbable-eng/grpc-web v0.15.0 github.com/miguelmota/go-ethereum-hdwallet v0.1.1 - github.com/onsi/ginkgo v1.16.5 + github.com/onsi/ginkgo/v2 v2.1.4 + github.com/onsi/gomega v1.19.0 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 github.com/regen-network/cosmos-proto v0.3.1 @@ -144,7 +145,7 @@ require ( golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.5 // indirect + golang.org/x/tools v0.1.10 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 // indirect diff --git a/go.sum b/go.sum index dd7d63dd69..03d958e20f 100644 --- a/go.sum +++ b/go.sum @@ -527,6 +527,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -871,15 +872,19 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -1187,6 +1192,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1264,6 +1270,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0= @@ -1311,6 +1318,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1372,6 +1380,7 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1518,6 +1527,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1620,8 +1630,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/proto/ethermint/feemarket/v1/feemarket.proto b/proto/ethermint/feemarket/v1/feemarket.proto index 1aab31e2a3..a244ca5d36 100644 --- a/proto/ethermint/feemarket/v1/feemarket.proto +++ b/proto/ethermint/feemarket/v1/feemarket.proto @@ -25,4 +25,9 @@ message Params { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; -} \ No newline at end of file + // min_gas_price defines the minimum gas price value for cosmos and eth transactions + string min_gas_price = 7 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/tests/e2e/integration_test.go b/tests/e2e/integration_test.go index a30c50a5b2..60398de336 100644 --- a/tests/e2e/integration_test.go +++ b/tests/e2e/integration_test.go @@ -15,7 +15,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" evmtypes "github.com/tharsis/ethermint/x/evm/types" - // . "github.com/onsi/ginkgo" + // . "github.com/onsi/ginkgo/v2" // . "github.com/onsi/gomega" "github.com/stretchr/testify/suite" diff --git a/testutil/fund.go b/testutil/fund.go new file mode 100644 index 0000000000..bcf73684fc --- /dev/null +++ b/testutil/fund.go @@ -0,0 +1,29 @@ +package testutil + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +// FundAccount is a utility function that funds an account by minting and +// sending the coins to the address. This should be used for testing purposes +// only! +func FundAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, amounts sdk.Coins) error { + if err := bankKeeper.MintCoins(ctx, evmtypes.ModuleName, amounts); err != nil { + return err + } + + return bankKeeper.SendCoinsFromModuleToAccount(ctx, evmtypes.ModuleName, addr, amounts) +} + +// FundModuleAccount is a utility function that funds a module account by +// minting and sending the coins to the address. This should be used for testing +// purposes only! +func FundModuleAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, recipientMod string, amounts sdk.Coins) error { + if err := bankKeeper.MintCoins(ctx, evmtypes.ModuleName, amounts); err != nil { + return err + } + + return bankKeeper.SendCoinsFromModuleToModule(ctx, evmtypes.ModuleName, recipientMod, amounts) +} diff --git a/tools/tools.go b/tools/tools.go index b529a369a5..72fad8be06 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -3,7 +3,7 @@ package tools import ( - _ "github.com/onsi/ginkgo/ginkgo" + _ "github.com/onsi/ginkgo/v2" ) // This file imports packages that are used when running go generate, or used diff --git a/x/feemarket/keeper/integration_test.go b/x/feemarket/keeper/integration_test.go new file mode 100644 index 0000000000..1ad9486dc4 --- /dev/null +++ b/x/feemarket/keeper/integration_test.go @@ -0,0 +1,655 @@ +package keeper_test + +import ( + "encoding/json" + "math/big" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/tx" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/tharsis/ethermint/app" + "github.com/tharsis/ethermint/crypto/ethsecp256k1" + "github.com/tharsis/ethermint/encoding" + "github.com/tharsis/ethermint/tests" + "github.com/tharsis/ethermint/testutil" + "github.com/tharsis/ethermint/x/feemarket/types" + + "github.com/cosmos/cosmos-sdk/simapp" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +var _ = Describe("Ethermint App min gas prices settings: ", func() { + var ( + privKey *ethsecp256k1.PrivKey + address sdk.AccAddress + msg banktypes.MsgSend + ) + + var setupChain = func(cliMinGasPricesStr string) { + // Initialize the app, so we can use SetMinGasPrices to set the + // validator-specific min-gas-prices setting + db := dbm.NewMemDB() + newapp := app.NewEthermintApp( + log.NewNopLogger(), + db, + nil, + true, + map[int64]bool{}, + app.DefaultNodeHome, + 5, + encoding.MakeConfig(app.ModuleBasics), + simapp.EmptyAppOptions{}, + baseapp.SetMinGasPrices(cliMinGasPricesStr), + ) + + genesisState := app.NewDefaultGenesisState() + genesisState[types.ModuleName] = newapp.AppCodec().MustMarshalJSON(types.DefaultGenesisState()) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + s.Require().NoError(err) + + // Initialize the chain + newapp.InitChain( + abci.RequestInitChain{ + ChainId: "ethermint_9000-1", + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + ConsensusParams: simapp.DefaultConsensusParams, + }, + ) + + s.app = newapp + s.SetupApp(false) + } + + var setupTest = func(cliMinGasPrices string) { + setupChain(cliMinGasPrices) + + privKey, address = generateKey() + amount, ok := sdk.NewIntFromString("10000000000000000000") + s.Require().True(ok) + initBalance := sdk.Coins{sdk.Coin{ + Denom: s.denom, + Amount: amount, + }} + testutil.FundAccount(s.app.BankKeeper, s.ctx, address, initBalance) + + msg = banktypes.MsgSend{ + FromAddress: address.String(), + ToAddress: address.String(), + Amount: sdk.Coins{sdk.Coin{ + Denom: s.denom, + Amount: sdk.NewInt(10000), + }}, + } + s.Commit() + } + + var setupContext = func(cliMinGasPrice string, minGasPrice sdk.Dec) { + setupTest(cliMinGasPrice + s.denom) + params := types.DefaultParams() + params.MinGasPrice = minGasPrice + s.app.FeeMarketKeeper.SetParams(s.ctx, params) + s.Commit() + } + + Context("with Cosmos transactions", func() { + Context("min-gas-prices (local) < MinGasPrices (feemarket param)", func() { + cliMinGasPrice := "1" + minGasPrice := sdk.NewDecWithPrec(3, 0) + BeforeEach(func() { + setupContext(cliMinGasPrice, minGasPrice) + }) + + Context("during CheckTx", func() { + It("should reject transactions with gasPrice < MinGasPrices", func() { + gasPrice := sdk.NewInt(2) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should accept transactions with gasPrice >= MinGasPrices", func() { + gasPrice := sdk.NewInt(3) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + }) + + Context("during DeliverTx", func() { + It("should reject transactions with gasPrice < MinGasPrices", func() { + gasPrice := sdk.NewInt(2) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should accept transactions with gasPrice >= MinGasPrices", func() { + gasPrice := sdk.NewInt(3) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + }) + }) + + Context("with min-gas-prices (local) == MinGasPrices (feemarket param)", func() { + cliMinGasPrice := "3" + minGasPrice := sdk.NewDecWithPrec(3, 0) + BeforeEach(func() { + setupContext(cliMinGasPrice, minGasPrice) + }) + + Context("during CheckTx", func() { + It("should reject transactions with gasPrice < min-gas-prices", func() { + gasPrice := sdk.NewInt(2) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "insufficient fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should accept transactions with gasPrice >= MinGasPrices", func() { + gasPrice := sdk.NewInt(3) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + }) + + Context("during DeliverTx", func() { + It("should reject transactions with gasPrice < MinGasPrices", func() { + gasPrice := sdk.NewInt(2) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should accept transactions with gasPrice >= MinGasPrices", func() { + gasPrice := sdk.NewInt(3) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + }) + }) + + Context("with MinGasPrices (feemarket param) < min-gas-prices (local)", func() { + cliMinGasPrice := "5" + minGasPrice := sdk.NewDecWithPrec(3, 0) + BeforeEach(func() { + setupContext(cliMinGasPrice, minGasPrice) + }) + Context("during CheckTx", func() { + It("should reject transactions with gasPrice < MinGasPrices", func() { + gasPrice := sdk.NewInt(2) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "insufficient fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should reject transactions with MinGasPrices < gasPrice < min-gas-prices", func() { + gasPrice := sdk.NewInt(4) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "insufficient fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should accept transactions with gasPrice > min-gas-prices", func() { + gasPrice := sdk.NewInt(5) + res := checkTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + }) + + Context("during DeliverTx", func() { + It("should reject transactions with gasPrice < MinGasPrices", func() { + gasPrice := sdk.NewInt(2) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }) + + It("should accept transactions with MinGasPrices < gasPrice < than min-gas-prices", func() { + gasPrice := sdk.NewInt(4) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + + It("should accept transactions with gasPrice >= min-gas-prices", func() { + gasPrice := sdk.NewInt(5) + res := deliverTx(privKey, &gasPrice, &msg) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }) + }) + }) + }) + + Context("with EVM transactions", func() { + type txParams struct { + gasPrice *big.Int + gasFeeCap *big.Int + gasTipCap *big.Int + accesses *ethtypes.AccessList + } + type getprices func() txParams + + getBaseFee := func() int64 { + paramsEvm := s.app.EvmKeeper.GetParams(s.ctx) + ethCfg := paramsEvm.ChainConfig.EthereumConfig(s.app.EvmKeeper.ChainID()) + return s.app.EvmKeeper.GetBaseFee(s.ctx, ethCfg).Int64() + } + Context("with BaseFee (feemarket) < MinGasPrices (feemarket param)", func() { + var baseFee int64 + BeforeEach(func() { + baseFee = getBaseFee() + setupContext("1", sdk.NewDecWithPrec(baseFee+30000000000, 0)) + }) + + Context("during CheckTx", func() { + DescribeTable("should reject transactions with EffectivePrice < MinGasPrices", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := checkEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(baseFee + 20000000000), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(baseFee + 20000000000), big.NewInt(0), ðtypes.AccessList{}} + }), + Entry("dynamic tx with GasFeeCap < MinGasPrices", func() txParams { + return txParams{nil, big.NewInt(baseFee + 29000000000), big.NewInt(29000000000), ðtypes.AccessList{}} + }), + Entry("dynamic tx with GasFeeCap > MinGasPrices, EffectivePrice < MinGasPrices", func() txParams { + return txParams{nil, big.NewInt(baseFee + 40000000000), big.NewInt(0), ðtypes.AccessList{}} + }), + ) + + DescribeTable("should accept transactions with gasPrice >= MinGasPrices", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := checkEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(baseFee + 31000000000), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(baseFee + 31000000000), big.NewInt(31000000000), ðtypes.AccessList{}} + }), + ) + }) + + Context("during DeliverTx", func() { + DescribeTable("should reject transactions with gasPrice < MinGasPrices", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := deliverEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(baseFee + 20000000000), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(baseFee + 20000000000), big.NewInt(0), ðtypes.AccessList{}} + }), + Entry("dynamic tx with GasFeeCap < MinGasPrices", func() txParams { + return txParams{nil, big.NewInt(baseFee + 29000000000), big.NewInt(29000000000), ðtypes.AccessList{}} + }), + Entry("dynamic tx with GasFeeCap > MinGasPrices, EffectivePrice < MinGasPrices", func() txParams { + return txParams{nil, big.NewInt(baseFee + 40000000000), big.NewInt(0), ðtypes.AccessList{}} + }), + ) + + DescribeTable("should accept transactions with gasPrice >= MinGasPrices", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := deliverEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(baseFee + 30000000001), nil, nil, nil} + }), + // the base fee decreases in this test, so we use a large gas tip + // to maintain an EffectivePrice > MinGasPrices + Entry("dynamic tx, EffectivePrice > MinGasPrices", func() txParams { + return txParams{nil, big.NewInt(baseFee + 30000000001), big.NewInt(30000000001), ðtypes.AccessList{}} + }), + ) + }) + }) + + Context("with MinGasPrices (feemarket param) < BaseFee (feemarket)", func() { + var baseFee int64 + BeforeEach(func() { + baseFee = getBaseFee() + s.Require().Greater(baseFee, int64(10)) + setupContext("5", sdk.NewDecWithPrec(10, 0)) + }) + + Context("during CheckTx", func() { + DescribeTable("should reject transactions with gasPrice < MinGasPrices", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := checkEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(2), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(2), big.NewInt(2), ðtypes.AccessList{}} + }), + ) + + DescribeTable("should reject transactions with MinGasPrices < tx gasPrice < EffectivePrice", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := checkEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "insufficient fee"), + ).To(BeTrue(), res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(20), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(baseFee - 1), big.NewInt(20), ðtypes.AccessList{}} + }), + ) + + DescribeTable("should accept transactions with gasPrice > EffectivePrice", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := checkEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(baseFee + 1000000000), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(baseFee + 1000000000), big.NewInt(10), ðtypes.AccessList{}} + }), + ) + }) + + Context("during DeliverTx", func() { + DescribeTable("should reject transactions with gasPrice < MinGasPrices", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := deliverEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "provided fee < minimum global fee"), + ).To(BeTrue(), res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(2), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(2), big.NewInt(2), ðtypes.AccessList{}} + }), + ) + + DescribeTable("should reject transactions with MinGasPrices < gasPrice < EffectivePrice", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := deliverEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(false), "transaction should have failed") + Expect( + strings.Contains(res.GetLog(), + "insufficient fee"), + ).To(BeTrue(), res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(20), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(20), big.NewInt(20), ðtypes.AccessList{}} + }), + ) + + DescribeTable("should accept transactions with gasPrice > EffectivePrice", + func(malleate getprices) { + p := malleate() + to := tests.GenerateAddress() + msgEthereumTx := buildEthTx(privKey, &to, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses) + res := deliverEthTx(privKey, msgEthereumTx) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + }, + Entry("legacy tx", func() txParams { + return txParams{big.NewInt(baseFee + 10), nil, nil, nil} + }), + Entry("dynamic tx", func() txParams { + return txParams{nil, big.NewInt(baseFee + 10), big.NewInt(10), ðtypes.AccessList{}} + }), + ) + }) + }) + }) +}) + +func generateKey() (*ethsecp256k1.PrivKey, sdk.AccAddress) { + address, priv := tests.NewAddrKey() + return priv.(*ethsecp256k1.PrivKey), sdk.AccAddress(address.Bytes()) +} + +func getNonce(addressBytes []byte) uint64 { + return s.app.EvmKeeper.GetNonce( + s.ctx, + common.BytesToAddress(addressBytes), + ) +} + +func buildEthTx( + priv *ethsecp256k1.PrivKey, + to *common.Address, + gasPrice *big.Int, + gasFeeCap *big.Int, + gasTipCap *big.Int, + accesses *ethtypes.AccessList, +) *evmtypes.MsgEthereumTx { + chainID := s.app.EvmKeeper.ChainID() + from := common.BytesToAddress(priv.PubKey().Address().Bytes()) + nonce := getNonce(from.Bytes()) + data := make([]byte, 0) + gasLimit := uint64(100000) + msgEthereumTx := evmtypes.NewTx( + chainID, + nonce, + to, + nil, + gasLimit, + gasPrice, + gasFeeCap, + gasTipCap, + data, + accesses, + ) + msgEthereumTx.From = from.String() + return msgEthereumTx +} + +func prepareEthTx(priv *ethsecp256k1.PrivKey, msgEthereumTx *evmtypes.MsgEthereumTx) []byte { + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) + s.Require().NoError(err) + + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) + s.Require().True(ok) + builder.SetExtensionOptions(option) + + err = msgEthereumTx.Sign(s.ethSigner, tests.NewSigner(priv)) + s.Require().NoError(err) + + err = txBuilder.SetMsgs(msgEthereumTx) + s.Require().NoError(err) + + txData, err := evmtypes.UnpackTxData(msgEthereumTx.Data) + s.Require().NoError(err) + + evmDenom := s.app.EvmKeeper.GetParams(s.ctx).EvmDenom + fees := sdk.Coins{{Denom: evmDenom, Amount: sdk.NewIntFromBigInt(txData.Fee())}} + builder.SetFeeAmount(fees) + builder.SetGasLimit(msgEthereumTx.GetGas()) + + // bz are bytes to be broadcasted over the network + bz, err := encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + return bz +} + +func deliverEthTx(priv *ethsecp256k1.PrivKey, msgEthereumTx *evmtypes.MsgEthereumTx) abci.ResponseDeliverTx { + bz := prepareEthTx(priv, msgEthereumTx) + req := abci.RequestDeliverTx{Tx: bz} + res := s.app.BaseApp.DeliverTx(req) + return res +} + +func checkEthTx(priv *ethsecp256k1.PrivKey, msgEthereumTx *evmtypes.MsgEthereumTx) abci.ResponseCheckTx { + bz := prepareEthTx(priv, msgEthereumTx) + req := abci.RequestCheckTx{Tx: bz} + res := s.app.BaseApp.CheckTx(req) + return res +} + +func prepareCosmosTx(priv *ethsecp256k1.PrivKey, gasPrice *sdk.Int, msgs ...sdk.Msg) []byte { + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + accountAddress := sdk.AccAddress(priv.PubKey().Address().Bytes()) + + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + + txBuilder.SetGasLimit(1000000) + if gasPrice == nil { + _gasPrice := sdk.NewInt(1) + gasPrice = &_gasPrice + } + fees := &sdk.Coins{{Denom: s.denom, Amount: gasPrice.MulRaw(1000000)}} + txBuilder.SetFeeAmount(*fees) + err := txBuilder.SetMsgs(msgs...) + s.Require().NoError(err) + + seq, err := s.app.AccountKeeper.GetSequence(s.ctx, accountAddress) + s.Require().NoError(err) + + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: encodingConfig.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: seq, + } + + sigsV2 := []signing.SignatureV2{sigV2} + + err = txBuilder.SetSignatures(sigsV2...) + s.Require().NoError(err) + + // Second round: all signer infos are set, so each signer can sign. + accNumber := s.app.AccountKeeper.GetAccount(s.ctx, accountAddress).GetAccountNumber() + signerData := authsigning.SignerData{ + ChainID: s.ctx.ChainID(), + AccountNumber: accNumber, + Sequence: seq, + } + sigV2, err = tx.SignWithPrivKey( + encodingConfig.TxConfig.SignModeHandler().DefaultMode(), signerData, + txBuilder, priv, encodingConfig.TxConfig, + seq, + ) + s.Require().NoError(err) + + sigsV2 = []signing.SignatureV2{sigV2} + err = txBuilder.SetSignatures(sigsV2...) + s.Require().NoError(err) + + // bz are bytes to be broadcasted over the network + bz, err := encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + return bz +} + +func deliverTx(priv *ethsecp256k1.PrivKey, gasPrice *sdk.Int, msgs ...sdk.Msg) abci.ResponseDeliverTx { + bz := prepareCosmosTx(priv, gasPrice, msgs...) + req := abci.RequestDeliverTx{Tx: bz} + res := s.app.BaseApp.DeliverTx(req) + return res +} + +func checkTx(priv *ethsecp256k1.PrivKey, gasPrice *sdk.Int, msgs ...sdk.Msg) abci.ResponseCheckTx { + bz := prepareCosmosTx(priv, gasPrice, msgs...) + req := abci.RequestCheckTx{Tx: bz} + res := s.app.BaseApp.CheckTx(req) + return res +} diff --git a/x/feemarket/keeper/keeper_test.go b/x/feemarket/keeper/keeper_test.go index 94a1fd0a0c..4404d27e08 100644 --- a/x/feemarket/keeper/keeper_test.go +++ b/x/feemarket/keeper/keeper_test.go @@ -6,6 +6,9 @@ import ( "testing" "time" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -15,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/tharsis/ethermint/app" @@ -22,12 +26,14 @@ import ( "github.com/tharsis/ethermint/encoding" "github.com/tharsis/ethermint/tests" ethermint "github.com/tharsis/ethermint/types" + evmtypes "github.com/tharsis/ethermint/x/evm/types" "github.com/tharsis/ethermint/x/feemarket/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" @@ -49,12 +55,29 @@ type KeeperTestSuite struct { appCodec codec.Codec signer keyring.Signer + denom string +} + +var s *KeeperTestSuite + +func TestKeeperTestSuite(t *testing.T) { + s = new(KeeperTestSuite) + suite.Run(t, s) + + // Run Ginkgo integration tests + RegisterFailHandler(Fail) + RunSpecs(t, "Keeper Suite") } -/// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`. -func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { +/// SetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`. +func (suite *KeeperTestSuite) SetupTest() { checkTx := false + suite.app = app.Setup(checkTx, nil) + suite.SetupApp(checkTx) +} +func (suite *KeeperTestSuite) SetupApp(checkTx bool) { + t := suite.T() // account key priv, err := ethsecp256k1.GenerateKey() require.NoError(t, err) @@ -66,7 +89,6 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { require.NoError(t, err) suite.consAddress = sdk.ConsAddress(priv.PubKey().Address()) - suite.app = app.Setup(checkTx, nil) suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{ Height: 1, ChainID: "ethermint_9000-1", @@ -104,8 +126,10 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { valAddr := sdk.ValAddress(suite.address.Bytes()) validator, err := stakingtypes.NewValidator(valAddr, priv.PubKey(), stakingtypes.Description{}) - err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) require.NoError(t, err) + validator = stakingkeeper.TestingUpdateValidator(suite.app.StakingKeeper, suite.ctx, validator, true) + suite.app.StakingKeeper.AfterValidatorCreated(suite.ctx, validator.GetOperator()) + err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) require.NoError(t, err) suite.app.StakingKeeper.SetValidator(suite.ctx, validator) @@ -114,14 +138,32 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) suite.appCodec = encodingConfig.Marshaler + suite.denom = evmtypes.DefaultEVMDenom } -func (suite *KeeperTestSuite) SetupTest() { - suite.DoSetupTest(suite.T()) +// Commit commits and starts a new block with an updated context. +func (suite *KeeperTestSuite) Commit() { + suite.CommitAfter(time.Second * 0) } -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) +// Commit commits a block at a given time. +func (suite *KeeperTestSuite) CommitAfter(t time.Duration) { + header := suite.ctx.BlockHeader() + suite.app.EndBlock(abci.RequestEndBlock{Height: header.Height}) + _ = suite.app.Commit() + + header.Height += 1 + header.Time = header.Time.Add(t) + suite.app.BeginBlock(abci.RequestBeginBlock{ + Header: header, + }) + + // update ctx + suite.ctx = suite.app.BaseApp.NewContext(false, header) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, suite.app.FeeMarketKeeper) + suite.queryClient = types.NewQueryClient(queryHelper) } func (suite *KeeperTestSuite) TestSetGetBlockGasUsed() { diff --git a/x/feemarket/keeper/migrations.go b/x/feemarket/keeper/migrations.go index 54efbfe6a7..ff5d3491bf 100644 --- a/x/feemarket/keeper/migrations.go +++ b/x/feemarket/keeper/migrations.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" v010 "github.com/tharsis/ethermint/x/feemarket/migrations/v010" + v011 "github.com/tharsis/ethermint/x/feemarket/migrations/v011" ) // Migrator is a struct for handling in-place store migrations. @@ -22,3 +23,8 @@ func NewMigrator(keeper Keeper) Migrator { func (m Migrator) Migrate1to2(ctx sdk.Context) error { return v010.MigrateStore(ctx, &m.keeper.paramSpace, m.keeper.storeKey) } + +// Migrate2to3 migrates the store from consensus version v2 to v3 +func (m Migrator) Migrate2to3(ctx sdk.Context) error { + return v011.MigrateStore(ctx, &m.keeper.paramSpace) +} diff --git a/x/feemarket/migrations/v010/migrate.go b/x/feemarket/migrations/v010/migrate.go index ed448cb190..6db1f55838 100644 --- a/x/feemarket/migrations/v010/migrate.go +++ b/x/feemarket/migrations/v010/migrate.go @@ -6,8 +6,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/tharsis/ethermint/x/feemarket/migrations/v010/types" v09types "github.com/tharsis/ethermint/x/feemarket/migrations/v09/types" - "github.com/tharsis/ethermint/x/feemarket/types" ) // KeyPrefixBaseFeeV1 is the base fee key prefix used in version 1 diff --git a/x/feemarket/migrations/v010/types/feemarket.pb.go b/x/feemarket/migrations/v010/types/feemarket.pb.go new file mode 100644 index 0000000000..62133fa3a2 --- /dev/null +++ b/x/feemarket/migrations/v010/types/feemarket.pb.go @@ -0,0 +1,472 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ethermint/feemarket/v1/feemarket.proto + +package types + +import ( + fmt "fmt" + github_com_cosmos_cosmos_sdk_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 + +// Params defines the EVM module parameters +type Params struct { + // no base fee forces the EIP-1559 base fee to 0 (needed for 0 price calls) + NoBaseFee bool `protobuf:"varint,1,opt,name=no_base_fee,json=noBaseFee,proto3" json:"no_base_fee,omitempty"` + // base fee change denominator bounds the amount the base fee can change + // between blocks. + BaseFeeChangeDenominator uint32 `protobuf:"varint,2,opt,name=base_fee_change_denominator,json=baseFeeChangeDenominator,proto3" json:"base_fee_change_denominator,omitempty"` + // elasticity multiplier bounds the maximum gas limit an EIP-1559 block may + // have. + ElasticityMultiplier uint32 `protobuf:"varint,3,opt,name=elasticity_multiplier,json=elasticityMultiplier,proto3" json:"elasticity_multiplier,omitempty"` + // height at which the base fee calculation is enabled. + EnableHeight int64 `protobuf:"varint,5,opt,name=enable_height,json=enableHeight,proto3" json:"enable_height,omitempty"` + // base fee for EIP-1559 blocks. + BaseFee github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=base_fee,json=baseFee,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"base_fee"` +} + +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_4feb8b20cf98e6e1, []int{0} +} +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) GetNoBaseFee() bool { + if m != nil { + return m.NoBaseFee + } + return false +} + +func (m *Params) GetBaseFeeChangeDenominator() uint32 { + if m != nil { + return m.BaseFeeChangeDenominator + } + return 0 +} + +func (m *Params) GetElasticityMultiplier() uint32 { + if m != nil { + return m.ElasticityMultiplier + } + return 0 +} + +func (m *Params) GetEnableHeight() int64 { + if m != nil { + return m.EnableHeight + } + return 0 +} + +var fileDescriptor_4feb8b20cf98e6e1 = []byte{ + // 343 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0xcf, 0x6a, 0xea, 0x40, + 0x14, 0x87, 0x33, 0xfe, 0xbb, 0x9a, 0x7b, 0x05, 0x09, 0xde, 0x4b, 0xb8, 0x85, 0x18, 0x5a, 0x90, + 0x6c, 0x9a, 0x20, 0xae, 0xbb, 0xb1, 0xa5, 0x68, 0xa1, 0x50, 0xb2, 0xec, 0x26, 0x4c, 0xe2, 0x31, + 0x19, 0x4c, 0x66, 0x64, 0xe6, 0x28, 0xf5, 0x2d, 0xfa, 0x10, 0x7d, 0x18, 0x97, 0x2e, 0x4b, 0x17, + 0x52, 0xf4, 0x45, 0x4a, 0xa3, 0x4d, 0x5c, 0xcd, 0xcc, 0xf9, 0xbe, 0xe1, 0x1c, 0xce, 0x4f, 0xef, + 0x03, 0x26, 0x20, 0x33, 0xc6, 0xd1, 0x9b, 0x01, 0x64, 0x54, 0xce, 0x01, 0xbd, 0xd5, 0xa0, 0x7c, + 0xb8, 0x0b, 0x29, 0x50, 0x18, 0xff, 0x0a, 0xcf, 0x2d, 0xd1, 0x6a, 0xf0, 0xbf, 0x1b, 0x8b, 0x58, + 0xe4, 0x8a, 0xf7, 0x7d, 0x3b, 0xda, 0x97, 0x6f, 0x15, 0xbd, 0xf1, 0x44, 0x25, 0xcd, 0x94, 0x61, + 0xe9, 0xbf, 0xb9, 0x08, 0x42, 0xaa, 0x20, 0x98, 0x01, 0x98, 0xc4, 0x26, 0x4e, 0xd3, 0x6f, 0x71, + 0x31, 0xa2, 0x0a, 0xee, 0x01, 0x8c, 0x1b, 0xfd, 0xe2, 0x07, 0x06, 0x51, 0x42, 0x79, 0x0c, 0xc1, + 0x14, 0xb8, 0xc8, 0x18, 0xa7, 0x28, 0xa4, 0x59, 0xb1, 0x89, 0xd3, 0xf6, 0xcd, 0xf0, 0x68, 0xdf, + 0xe6, 0xc2, 0x5d, 0xc9, 0x8d, 0xa1, 0xfe, 0x17, 0x52, 0xaa, 0x90, 0x45, 0x0c, 0xd7, 0x41, 0xb6, + 0x4c, 0x91, 0x2d, 0x52, 0x06, 0xd2, 0xac, 0xe6, 0x1f, 0xbb, 0x25, 0x7c, 0x2c, 0x98, 0x71, 0xa5, + 0xb7, 0x81, 0xd3, 0x30, 0x85, 0x20, 0x01, 0x16, 0x27, 0x68, 0xd6, 0x6d, 0xe2, 0x54, 0xfd, 0x3f, + 0xc7, 0xe2, 0x38, 0xaf, 0x19, 0x13, 0xbd, 0x59, 0x4c, 0xdd, 0xb0, 0x89, 0xd3, 0x1a, 0xb9, 0x9b, + 0x5d, 0x4f, 0xfb, 0xd8, 0xf5, 0xfa, 0x31, 0xc3, 0x64, 0x19, 0xba, 0x91, 0xc8, 0xbc, 0x48, 0xa8, + 0x4c, 0xa8, 0xd3, 0x71, 0xad, 0xa6, 0x73, 0x0f, 0xd7, 0x0b, 0x50, 0xee, 0x84, 0xa3, 0xff, 0xeb, + 0x34, 0xf5, 0x43, 0xad, 0x59, 0xeb, 0xd4, 0xfd, 0x0e, 0xe3, 0x0c, 0x19, 0x4d, 0x8b, 0x65, 0x8c, + 0xc6, 0x9b, 0xbd, 0x45, 0xb6, 0x7b, 0x8b, 0x7c, 0xee, 0x2d, 0xf2, 0x7a, 0xb0, 0xb4, 0xed, 0xc1, + 0xd2, 0xde, 0x0f, 0x96, 0xf6, 0xec, 0x9e, 0xb5, 0xc0, 0x84, 0x4a, 0xc5, 0x94, 0x57, 0x26, 0xf5, + 0x72, 0x96, 0x55, 0xde, 0x2e, 0x6c, 0xe4, 0x7b, 0x1f, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0xee, + 0xfc, 0x5c, 0x06, 0xcf, 0x01, 0x00, 0x00, +} + +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 + { + size := m.BaseFee.Size() + i -= size + if _, err := m.BaseFee.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFeemarket(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + if m.EnableHeight != 0 { + i = encodeVarintFeemarket(dAtA, i, uint64(m.EnableHeight)) + i-- + dAtA[i] = 0x28 + } + if m.ElasticityMultiplier != 0 { + i = encodeVarintFeemarket(dAtA, i, uint64(m.ElasticityMultiplier)) + i-- + dAtA[i] = 0x18 + } + if m.BaseFeeChangeDenominator != 0 { + i = encodeVarintFeemarket(dAtA, i, uint64(m.BaseFeeChangeDenominator)) + i-- + dAtA[i] = 0x10 + } + if m.NoBaseFee { + i-- + if m.NoBaseFee { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintFeemarket(dAtA []byte, offset int, v uint64) int { + offset -= sovFeemarket(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NoBaseFee { + n += 2 + } + if m.BaseFeeChangeDenominator != 0 { + n += 1 + sovFeemarket(uint64(m.BaseFeeChangeDenominator)) + } + if m.ElasticityMultiplier != 0 { + n += 1 + sovFeemarket(uint64(m.ElasticityMultiplier)) + } + if m.EnableHeight != 0 { + n += 1 + sovFeemarket(uint64(m.EnableHeight)) + } + l = m.BaseFee.Size() + n += 1 + l + sovFeemarket(uint64(l)) + return n +} + +func sovFeemarket(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozFeemarket(x uint64) (n int) { + return sovFeemarket(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +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 ErrIntOverflowFeemarket + } + 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 != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NoBaseFee", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeemarket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.NoBaseFee = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseFeeChangeDenominator", wireType) + } + m.BaseFeeChangeDenominator = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeemarket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BaseFeeChangeDenominator |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ElasticityMultiplier", wireType) + } + m.ElasticityMultiplier = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeemarket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ElasticityMultiplier |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EnableHeight", wireType) + } + m.EnableHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeemarket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EnableHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseFee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeemarket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFeemarket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFeemarket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.BaseFee.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFeemarket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthFeemarket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipFeemarket(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, ErrIntOverflowFeemarket + } + 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, ErrIntOverflowFeemarket + } + 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, ErrIntOverflowFeemarket + } + 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, ErrInvalidLengthFeemarket + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupFeemarket + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthFeemarket + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthFeemarket = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowFeemarket = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupFeemarket = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/feemarket/migrations/v010/types/genesis.pb.go b/x/feemarket/migrations/v010/types/genesis.pb.go new file mode 100644 index 0000000000..586b8057d7 --- /dev/null +++ b/x/feemarket/migrations/v010/types/genesis.pb.go @@ -0,0 +1,356 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ethermint/feemarket/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "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 defines the feemarket module's genesis state. +type GenesisState struct { + // params defines all the paramaters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + // block gas is the amount of gas used on the last block before the upgrade. + // Zero by default. + BlockGas uint64 `protobuf:"varint,3,opt,name=block_gas,json=blockGas,proto3" json:"block_gas,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_6241c21661288629, []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{} +} + +func (m *GenesisState) GetBlockGas() uint64 { + if m != nil { + return m.BlockGas + } + return 0 +} + +var fileDescriptor_6241c21661288629 = []byte{ + // 246 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x49, 0x2d, 0xc9, 0x48, + 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x4f, 0x4b, 0x4d, 0xcd, 0x4d, 0x2c, 0xca, 0x4e, 0x2d, 0xd1, + 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0x12, 0x83, 0xab, 0xd2, 0x83, 0xab, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, + 0x07, 0x2b, 0xd1, 0x07, 0xb1, 0x20, 0xaa, 0xa5, 0xd4, 0x70, 0x98, 0x89, 0xd0, 0x0a, 0x56, 0xa7, + 0x54, 0xc9, 0xc5, 0xe3, 0x0e, 0xb1, 0x26, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0xc8, 0x86, 0x8b, 0xad, + 0x20, 0xb1, 0x28, 0x31, 0xb7, 0x58, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x4e, 0x0f, 0xbb, + 0xb5, 0x7a, 0x01, 0x60, 0x55, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0xf5, 0x08, 0x49, + 0x73, 0x71, 0x26, 0xe5, 0xe4, 0x27, 0x67, 0xc7, 0xa7, 0x27, 0x16, 0x4b, 0x30, 0x2b, 0x30, 0x6a, + 0xb0, 0x04, 0x71, 0x80, 0x05, 0xdc, 0x13, 0x8b, 0xbd, 0x58, 0x38, 0x98, 0x04, 0x98, 0x83, 0x38, + 0x92, 0x12, 0x8b, 0x53, 0xe3, 0xd3, 0x52, 0x53, 0x9d, 0x3c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, + 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, + 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x2f, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, + 0xbf, 0x24, 0x23, 0xb1, 0xa8, 0x38, 0xb3, 0x58, 0x1f, 0xe1, 0x9f, 0x0a, 0x24, 0x1f, 0x95, 0x54, + 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0xfd, 0x62, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x3c, 0x4e, + 0x2a, 0x68, 0x49, 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 + if m.BlockGas != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.BlockGas)) + i-- + dAtA[i] = 0x18 + } + { + 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 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)) + if m.BlockGas != 0 { + n += 1 + sovGenesis(uint64(m.BlockGas)) + } + 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 + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockGas", wireType) + } + m.BlockGas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockGas |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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/feemarket/migrations/v010/types/params.go b/x/feemarket/migrations/v010/types/params.go new file mode 100644 index 0000000000..732ba4378f --- /dev/null +++ b/x/feemarket/migrations/v010/types/params.go @@ -0,0 +1,134 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/ethereum/go-ethereum/params" +) + +var _ paramtypes.ParamSet = &Params{} + +// Parameter keys +var ( + ParamStoreKeyNoBaseFee = []byte("NoBaseFee") + ParamStoreKeyBaseFeeChangeDenominator = []byte("BaseFeeChangeDenominator") + ParamStoreKeyElasticityMultiplier = []byte("ElasticityMultiplier") + ParamStoreKeyBaseFee = []byte("BaseFee") + ParamStoreKeyEnableHeight = []byte("EnableHeight") +) + +// ParamKeyTable returns the parameter key table. +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// NewParams creates a new Params instance +func NewParams(noBaseFee bool, baseFeeChangeDenom, elasticityMultiplier uint32, baseFee uint64, enableHeight int64) Params { + return Params{ + NoBaseFee: noBaseFee, + BaseFeeChangeDenominator: baseFeeChangeDenom, + ElasticityMultiplier: elasticityMultiplier, + BaseFee: sdk.NewIntFromUint64(baseFee), + EnableHeight: enableHeight, + } +} + +// DefaultParams returns default evm parameters +func DefaultParams() Params { + return Params{ + NoBaseFee: false, + BaseFeeChangeDenominator: params.BaseFeeChangeDenominator, + ElasticityMultiplier: params.ElasticityMultiplier, + BaseFee: sdk.NewIntFromUint64(params.InitialBaseFee), + EnableHeight: 0, + } +} + +// ParamSetPairs returns the parameter set pairs. +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(ParamStoreKeyNoBaseFee, &p.NoBaseFee, validateBool), + paramtypes.NewParamSetPair(ParamStoreKeyBaseFeeChangeDenominator, &p.BaseFeeChangeDenominator, validateBaseFeeChangeDenominator), + paramtypes.NewParamSetPair(ParamStoreKeyElasticityMultiplier, &p.ElasticityMultiplier, validateElasticityMultiplier), + paramtypes.NewParamSetPair(ParamStoreKeyBaseFee, &p.BaseFee, validateBaseFee), + paramtypes.NewParamSetPair(ParamStoreKeyEnableHeight, &p.EnableHeight, validateEnableHeight), + } +} + +// Validate performs basic validation on fee market parameters. +func (p Params) Validate() error { + if p.BaseFeeChangeDenominator == 0 { + return fmt.Errorf("base fee change denominator cannot be 0") + } + + if p.BaseFee.IsNegative() { + return fmt.Errorf("initial base fee cannot be negative: %s", p.BaseFee) + } + + if p.EnableHeight < 0 { + return fmt.Errorf("enable height cannot be negative: %d", p.EnableHeight) + } + + return nil +} + +func (p *Params) IsBaseFeeEnabled(height int64) bool { + return !p.NoBaseFee && height >= p.EnableHeight +} + +func validateBool(i interface{}) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + return nil +} + +func validateBaseFeeChangeDenominator(i interface{}) error { + value, ok := i.(uint32) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if value == 0 { + return fmt.Errorf("base fee change denominator cannot be 0") + } + + return nil +} + +func validateElasticityMultiplier(i interface{}) error { + _, ok := i.(uint32) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + return nil +} + +func validateBaseFee(i interface{}) error { + value, ok := i.(sdk.Int) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if value.IsNegative() { + return fmt.Errorf("base fee cannot be negative") + } + + return nil +} + +func validateEnableHeight(i interface{}) error { + value, ok := i.(int64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if value < 0 { + return fmt.Errorf("enable height cannot be negative: %d", value) + } + + return nil +} diff --git a/x/feemarket/migrations/v011/migrate.go b/x/feemarket/migrations/v011/migrate.go new file mode 100644 index 0000000000..90b1d11db9 --- /dev/null +++ b/x/feemarket/migrations/v011/migrate.go @@ -0,0 +1,37 @@ +package v011 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + v010types "github.com/tharsis/ethermint/x/feemarket/migrations/v010/types" + "github.com/tharsis/ethermint/x/feemarket/types" +) + +// MigrateStore adds the MinGasPrice param with a value of 0 +func MigrateStore(ctx sdk.Context, paramstore *paramtypes.Subspace) error { + if !paramstore.HasKeyTable() { + ps := paramstore.WithKeyTable(types.ParamKeyTable()) + paramstore = &ps + } + + paramstore.Set(ctx, types.ParamStoreKeyMinGasPrice, sdk.ZeroDec()) + return nil +} + +// MigrateJSON accepts exported v0.10 x/feemarket genesis state and migrates it to +// v0.11 x/feemarket genesis state. The migration includes: +// - add MinGasPrice param +func MigrateJSON(oldState v010types.GenesisState) types.GenesisState { + return types.GenesisState{ + Params: types.Params{ + NoBaseFee: oldState.Params.NoBaseFee, + BaseFeeChangeDenominator: oldState.Params.BaseFeeChangeDenominator, + ElasticityMultiplier: oldState.Params.ElasticityMultiplier, + EnableHeight: oldState.Params.EnableHeight, + BaseFee: oldState.Params.BaseFee, + MinGasPrice: sdk.ZeroDec(), + }, + BlockGas: oldState.BlockGas, + } +} diff --git a/x/feemarket/migrations/v011/migrate_test.go b/x/feemarket/migrations/v011/migrate_test.go new file mode 100644 index 0000000000..46729e2aaf --- /dev/null +++ b/x/feemarket/migrations/v011/migrate_test.go @@ -0,0 +1,73 @@ +package v011_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/tharsis/ethermint/encoding" + + "github.com/tharsis/ethermint/app" + v010types "github.com/tharsis/ethermint/x/feemarket/migrations/v010/types" + v011 "github.com/tharsis/ethermint/x/feemarket/migrations/v011" + feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" +) + +func TestMigrateStore(t *testing.T) { + encCfg := encoding.MakeConfig(app.ModuleBasics) + feemarketKey := sdk.NewKVStoreKey(feemarkettypes.StoreKey) + tFeeMarketKey := sdk.NewTransientStoreKey(fmt.Sprintf("%s_test", feemarkettypes.StoreKey)) + ctx := testutil.DefaultContext(feemarketKey, tFeeMarketKey) + paramstore := paramtypes.NewSubspace( + encCfg.Marshaler, encCfg.Amino, feemarketKey, tFeeMarketKey, "feemarket", + ) + + paramstore = paramstore.WithKeyTable(feemarkettypes.ParamKeyTable()) + require.True(t, paramstore.HasKeyTable()) + + // check no MinGasPrice param + require.False(t, paramstore.Has(ctx, feemarkettypes.ParamStoreKeyMinGasPrice)) + + // Run migrations + err := v011.MigrateStore(ctx, ¶mstore) + require.NoError(t, err) + + // Make sure the params are set + require.True(t, paramstore.Has(ctx, feemarkettypes.ParamStoreKeyMinGasPrice)) + + var minGasPrice sdk.Dec + + // Make sure the new params are set + require.NotPanics(t, func() { + paramstore.Get(ctx, feemarkettypes.ParamStoreKeyMinGasPrice, &minGasPrice) + }) + + // check the params are updated + require.True(t, minGasPrice.IsZero()) +} + +func TestMigrateJSON(t *testing.T) { + rawJson := `{ + "block_gas": "0", + "params": { + "base_fee_change_denominator": 8, + "elasticity_multiplier": 2, + "enable_height": "0", + "base_fee": "1000000000", + "no_base_fee": false + } + }` + encCfg := encoding.MakeConfig(app.ModuleBasics) + var genState v010types.GenesisState + err := encCfg.Marshaler.UnmarshalJSON([]byte(rawJson), &genState) + require.NoError(t, err) + + migratedGenState := v011.MigrateJSON(genState) + + require.True(t, migratedGenState.Params.MinGasPrice.IsZero()) +} diff --git a/x/feemarket/module.go b/x/feemarket/module.go index 71e0c6d0d7..c1757189a4 100644 --- a/x/feemarket/module.go +++ b/x/feemarket/module.go @@ -44,7 +44,7 @@ func (AppModuleBasic) RegisterLegacyAminoCodec(_ *codec.LegacyAmino) { // ConsensusVersion returns the consensus state-breaking version for the module. func (AppModuleBasic) ConsensusVersion() uint64 { - return 2 + return 3 } // DefaultGenesis returns default genesis state as raw bytes for the fee market @@ -122,6 +122,11 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { if err != nil { panic(err) } + + err = cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3) + if err != nil { + panic(err) + } } // Route returns the message routing key for the fee market module. diff --git a/x/feemarket/simulation/genesis.go b/x/feemarket/simulation/genesis.go index b5b5c0ec7f..bbb94f63ed 100644 --- a/x/feemarket/simulation/genesis.go +++ b/x/feemarket/simulation/genesis.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/tharsis/ethermint/x/feemarket/types" @@ -11,7 +12,8 @@ import ( // RandomizedGenState generates a random GenesisState for nft func RandomizedGenState(simState *module.SimulationState) { - params := types.NewParams(simState.Rand.Uint32()%2 == 0, simState.Rand.Uint32(), simState.Rand.Uint32(), simState.Rand.Uint64(), simState.Rand.Int63()) + params := types.NewParams(simState.Rand.Uint32()%2 == 0, simState.Rand.Uint32(), simState.Rand.Uint32(), simState.Rand.Uint64(), simState.Rand.Int63(), sdk.ZeroDec()) + blockGas := simState.Rand.Uint64() feemarketGenesis := types.NewGenesisState(params, blockGas) diff --git a/x/feemarket/spec/01_concepts.md b/x/feemarket/spec/01_concepts.md index 99d0e91ad8..3605310690 100644 --- a/x/feemarket/spec/01_concepts.md +++ b/x/feemarket/spec/01_concepts.md @@ -33,3 +33,6 @@ Transactions specify a maximum fee per gas they are willing to pay total (aka: m Reference: [EIP1559](https://eips.ethereum.org/EIPS/eip-1559) +## Global Minimum Gas Price + +The minimum gas price needed for transactions to be processed. It applies to both Cosmos and EVM transactions. Governance can change this `feemarket` module parameter value. If the effective gas price or the minimum gas price is lower than the global `MinGasPrice` (`min-gas-price (local) < MinGasPrice (global) OR EffectiveGasPrice < MinGasPrice`), then `MinGasPrice` is used as a lower bound. If transactions are rejected due to having a gas price lower than `MinGasPrice`, users need to resend the transactions with a gas price higher than `MinGasPrice`. In the case of EIP-1559 (dynamic fee transactions), users must increase the priority fee for their transactions to be valid. diff --git a/x/feemarket/spec/07_params.md b/x/feemarket/spec/07_params.md index 2a1c4cbaaa..f10f1c06f6 100644 --- a/x/feemarket/spec/07_params.md +++ b/x/feemarket/spec/07_params.md @@ -11,4 +11,5 @@ The `x/feemarket` module contains the following parameters: | BaseFeeChangeDenominator | uint32 | 8 | bounds the amount the base fee that can change between blocks | | ElasticityMultiplier | uint32 | 2 | bounds the threshold which the base fee will increase or decrease depending on the total gas used in the previous block| | BaseFee | uint32 | 1000000000 | base fee for EIP-1559 blocks | -| EnableHeight | uint32 | 0 | height which enable fee adjustment | \ No newline at end of file +| EnableHeight | uint32 | 0 | height which enable fee adjustment | +| MinGasPrice | sdk.Dec | 0 | global minimum gas price that needs to be paid to include a transaction in a block | diff --git a/x/feemarket/spec/09_antehandlers.md b/x/feemarket/spec/09_antehandlers.md new file mode 100644 index 0000000000..f6f544d40c --- /dev/null +++ b/x/feemarket/spec/09_antehandlers.md @@ -0,0 +1,25 @@ + + +# AnteHandlers + +The `x/feemarket` module provides `AnteDecorator`s that are recursively chained together into a single [`Antehandler`](https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-alpha1/docs/architecture/adr-010-modular-antehandler.md). These decorators perform basic validity checks on an Ethereum or Cosmos SDK transaction, such that it could be thrown out of the transaction Mempool. + +Note that the `AnteHandler` is run for every transaction and called on both `CheckTx` and `DeliverTx`. + +## Decorators + +### `MinGasPriceDecorator` + +Rejects Cosmos SDK transactions with transaction fees lower than `MinGasPrice * GasLimit`. + +### `EthMinGasPriceDecorator` + +Rejects EVM transactions with transactions fees lower than `MinGasPrice * GasLimit`. + - For `LegacyTx` and `AccessListTx`, the `GasPrice * GasLimit` is used. + - For EIP-1559 (*aka.* `DynamicFeeTx`), the `EffectivePrice * GasLimit` is used. + +::: tip +**Note**: For dynamic transactions, if the `feemarket` formula results in a `BaseFee` that lowers `EffectivePrice < MinGasPrices`, the users must increase the `GasTipCap` (priority fee) until `EffectivePrice > MinGasPrices`. Transactions with `MinGasPrices * GasLimit < transaction fee < EffectiveFee` are rejected by the `feemarket` `AnteHandle`. +::: diff --git a/x/feemarket/spec/09_future_improvements.md b/x/feemarket/spec/10_future_improvements.md similarity index 100% rename from x/feemarket/spec/09_future_improvements.md rename to x/feemarket/spec/10_future_improvements.md diff --git a/x/feemarket/types/feemarket.pb.go b/x/feemarket/types/feemarket.pb.go index a445ff5439..b8dde3bcf0 100644 --- a/x/feemarket/types/feemarket.pb.go +++ b/x/feemarket/types/feemarket.pb.go @@ -38,6 +38,8 @@ type Params struct { EnableHeight int64 `protobuf:"varint,5,opt,name=enable_height,json=enableHeight,proto3" json:"enable_height,omitempty"` // base fee for EIP-1559 blocks. BaseFee github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=base_fee,json=baseFee,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"base_fee"` + // min_gas_price defines the minimum gas price value for cosmos and eth transactions + MinGasPrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,7,opt,name=min_gas_price,json=minGasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_gas_price"` } func (m *Params) Reset() { *m = Params{} } @@ -110,29 +112,31 @@ func init() { } var fileDescriptor_4feb8b20cf98e6e1 = []byte{ - // 343 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0xcf, 0x6a, 0xea, 0x40, - 0x14, 0x87, 0x33, 0xfe, 0xbb, 0x9a, 0x7b, 0x05, 0x09, 0xde, 0x4b, 0xb8, 0x85, 0x18, 0x5a, 0x90, - 0x6c, 0x9a, 0x20, 0xae, 0xbb, 0xb1, 0xa5, 0x68, 0xa1, 0x50, 0xb2, 0xec, 0x26, 0x4c, 0xe2, 0x31, - 0x19, 0x4c, 0x66, 0x64, 0xe6, 0x28, 0xf5, 0x2d, 0xfa, 0x10, 0x7d, 0x18, 0x97, 0x2e, 0x4b, 0x17, - 0x52, 0xf4, 0x45, 0x4a, 0xa3, 0x4d, 0x5c, 0xcd, 0xcc, 0xf9, 0xbe, 0xe1, 0x1c, 0xce, 0x4f, 0xef, - 0x03, 0x26, 0x20, 0x33, 0xc6, 0xd1, 0x9b, 0x01, 0x64, 0x54, 0xce, 0x01, 0xbd, 0xd5, 0xa0, 0x7c, - 0xb8, 0x0b, 0x29, 0x50, 0x18, 0xff, 0x0a, 0xcf, 0x2d, 0xd1, 0x6a, 0xf0, 0xbf, 0x1b, 0x8b, 0x58, - 0xe4, 0x8a, 0xf7, 0x7d, 0x3b, 0xda, 0x97, 0x6f, 0x15, 0xbd, 0xf1, 0x44, 0x25, 0xcd, 0x94, 0x61, - 0xe9, 0xbf, 0xb9, 0x08, 0x42, 0xaa, 0x20, 0x98, 0x01, 0x98, 0xc4, 0x26, 0x4e, 0xd3, 0x6f, 0x71, - 0x31, 0xa2, 0x0a, 0xee, 0x01, 0x8c, 0x1b, 0xfd, 0xe2, 0x07, 0x06, 0x51, 0x42, 0x79, 0x0c, 0xc1, - 0x14, 0xb8, 0xc8, 0x18, 0xa7, 0x28, 0xa4, 0x59, 0xb1, 0x89, 0xd3, 0xf6, 0xcd, 0xf0, 0x68, 0xdf, - 0xe6, 0xc2, 0x5d, 0xc9, 0x8d, 0xa1, 0xfe, 0x17, 0x52, 0xaa, 0x90, 0x45, 0x0c, 0xd7, 0x41, 0xb6, - 0x4c, 0x91, 0x2d, 0x52, 0x06, 0xd2, 0xac, 0xe6, 0x1f, 0xbb, 0x25, 0x7c, 0x2c, 0x98, 0x71, 0xa5, - 0xb7, 0x81, 0xd3, 0x30, 0x85, 0x20, 0x01, 0x16, 0x27, 0x68, 0xd6, 0x6d, 0xe2, 0x54, 0xfd, 0x3f, - 0xc7, 0xe2, 0x38, 0xaf, 0x19, 0x13, 0xbd, 0x59, 0x4c, 0xdd, 0xb0, 0x89, 0xd3, 0x1a, 0xb9, 0x9b, - 0x5d, 0x4f, 0xfb, 0xd8, 0xf5, 0xfa, 0x31, 0xc3, 0x64, 0x19, 0xba, 0x91, 0xc8, 0xbc, 0x48, 0xa8, - 0x4c, 0xa8, 0xd3, 0x71, 0xad, 0xa6, 0x73, 0x0f, 0xd7, 0x0b, 0x50, 0xee, 0x84, 0xa3, 0xff, 0xeb, - 0x34, 0xf5, 0x43, 0xad, 0x59, 0xeb, 0xd4, 0xfd, 0x0e, 0xe3, 0x0c, 0x19, 0x4d, 0x8b, 0x65, 0x8c, - 0xc6, 0x9b, 0xbd, 0x45, 0xb6, 0x7b, 0x8b, 0x7c, 0xee, 0x2d, 0xf2, 0x7a, 0xb0, 0xb4, 0xed, 0xc1, - 0xd2, 0xde, 0x0f, 0x96, 0xf6, 0xec, 0x9e, 0xb5, 0xc0, 0x84, 0x4a, 0xc5, 0x94, 0x57, 0x26, 0xf5, - 0x72, 0x96, 0x55, 0xde, 0x2e, 0x6c, 0xe4, 0x7b, 0x1f, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0xee, - 0xfc, 0x5c, 0x06, 0xcf, 0x01, 0x00, 0x00, + // 371 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0x4f, 0xeb, 0xda, 0x30, + 0x18, 0xc7, 0x9b, 0xdf, 0x1f, 0xff, 0xc4, 0x09, 0x52, 0xdc, 0x28, 0x1b, 0xd4, 0xb2, 0x81, 0xf4, + 0xb2, 0x16, 0xf1, 0xbc, 0x8b, 0x93, 0x4d, 0x07, 0x03, 0xe9, 0x71, 0x97, 0x90, 0xd6, 0xc7, 0x36, + 0xd8, 0x24, 0x25, 0x89, 0x32, 0xdf, 0xc5, 0x5e, 0x96, 0x47, 0x8f, 0x63, 0x07, 0x19, 0xfa, 0x26, + 0x76, 0x1c, 0x56, 0xd7, 0x7a, 0xdd, 0xa9, 0xcd, 0xf3, 0xf9, 0xe4, 0x79, 0x1e, 0xf2, 0xc5, 0x43, + 0x30, 0x19, 0x28, 0xce, 0x84, 0x09, 0x57, 0x00, 0x9c, 0xaa, 0x35, 0x98, 0x70, 0x3b, 0xaa, 0x0f, + 0x41, 0xa1, 0xa4, 0x91, 0xf6, 0xab, 0xca, 0x0b, 0x6a, 0xb4, 0x1d, 0xbd, 0xee, 0xa7, 0x32, 0x95, + 0xa5, 0x12, 0x5e, 0xfe, 0xae, 0xf6, 0xdb, 0x3f, 0x0f, 0xb8, 0xb1, 0xa0, 0x8a, 0x72, 0x6d, 0xbb, + 0xb8, 0x23, 0x24, 0x89, 0xa9, 0x06, 0xb2, 0x02, 0x70, 0x90, 0x87, 0xfc, 0x56, 0xd4, 0x16, 0x72, + 0x42, 0x35, 0x7c, 0x02, 0xb0, 0x3f, 0xe0, 0x37, 0xff, 0x20, 0x49, 0x32, 0x2a, 0x52, 0x20, 0x4b, + 0x10, 0x92, 0x33, 0x41, 0x8d, 0x54, 0xce, 0x83, 0x87, 0xfc, 0x6e, 0xe4, 0xc4, 0x57, 0xfb, 0x63, + 0x29, 0x4c, 0x6b, 0x6e, 0x8f, 0xf1, 0x4b, 0xc8, 0xa9, 0x36, 0x2c, 0x61, 0x66, 0x47, 0xf8, 0x26, + 0x37, 0xac, 0xc8, 0x19, 0x28, 0xe7, 0xb1, 0xbc, 0xd8, 0xaf, 0xe1, 0xd7, 0x8a, 0xd9, 0xef, 0x70, + 0x17, 0x04, 0x8d, 0x73, 0x20, 0x19, 0xb0, 0x34, 0x33, 0xce, 0xb3, 0x87, 0xfc, 0xc7, 0xe8, 0xc5, + 0xb5, 0x38, 0x2b, 0x6b, 0xf6, 0x1c, 0xb7, 0xaa, 0xad, 0x1b, 0x1e, 0xf2, 0xdb, 0x93, 0x60, 0x7f, + 0x1c, 0x58, 0xbf, 0x8e, 0x83, 0x61, 0xca, 0x4c, 0xb6, 0x89, 0x83, 0x44, 0xf2, 0x30, 0x91, 0x9a, + 0x4b, 0x7d, 0xfb, 0xbc, 0xd7, 0xcb, 0x75, 0x68, 0x76, 0x05, 0xe8, 0x60, 0x2e, 0x4c, 0xd4, 0xbc, + 0x6d, 0x6d, 0x47, 0xb8, 0xcb, 0x99, 0x20, 0x29, 0xd5, 0xa4, 0x50, 0x2c, 0x01, 0xa7, 0xf9, 0xdf, + 0xfd, 0xa6, 0x90, 0x44, 0x1d, 0xce, 0xc4, 0x67, 0xaa, 0x17, 0x97, 0x16, 0x5f, 0x9e, 0x5a, 0x4f, + 0xbd, 0xe7, 0xa8, 0xc7, 0x04, 0x33, 0x8c, 0xe6, 0xd5, 0x03, 0x4f, 0x66, 0xfb, 0x93, 0x8b, 0x0e, + 0x27, 0x17, 0xfd, 0x3e, 0xb9, 0xe8, 0xc7, 0xd9, 0xb5, 0x0e, 0x67, 0xd7, 0xfa, 0x79, 0x76, 0xad, + 0x6f, 0xc1, 0xdd, 0x18, 0x93, 0x51, 0xa5, 0x99, 0x0e, 0xeb, 0xf4, 0xbf, 0xdf, 0xe5, 0x5f, 0x8e, + 0x8c, 0x1b, 0x65, 0x96, 0xe3, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x3c, 0x5d, 0x33, 0x70, 0x23, + 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -155,6 +159,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.MinGasPrice.Size() + i -= size + if _, err := m.MinGasPrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFeemarket(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a { size := m.BaseFee.Size() i -= size @@ -224,6 +238,8 @@ func (m *Params) Size() (n int) { } l = m.BaseFee.Size() n += 1 + l + sovFeemarket(uint64(l)) + l = m.MinGasPrice.Size() + n += 1 + l + sovFeemarket(uint64(l)) return n } @@ -373,6 +389,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinGasPrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeemarket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFeemarket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFeemarket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinGasPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipFeemarket(dAtA[iNdEx:]) diff --git a/x/feemarket/types/params.go b/x/feemarket/types/params.go index 732ba4378f..b03fc4d146 100644 --- a/x/feemarket/types/params.go +++ b/x/feemarket/types/params.go @@ -17,6 +17,7 @@ var ( ParamStoreKeyElasticityMultiplier = []byte("ElasticityMultiplier") ParamStoreKeyBaseFee = []byte("BaseFee") ParamStoreKeyEnableHeight = []byte("EnableHeight") + ParamStoreKeyMinGasPrice = []byte("MinGasPrice") ) // ParamKeyTable returns the parameter key table. @@ -25,13 +26,21 @@ func ParamKeyTable() paramtypes.KeyTable { } // NewParams creates a new Params instance -func NewParams(noBaseFee bool, baseFeeChangeDenom, elasticityMultiplier uint32, baseFee uint64, enableHeight int64) Params { +func NewParams( + noBaseFee bool, + baseFeeChangeDenom, + elasticityMultiplier uint32, + baseFee uint64, + enableHeight int64, + minGasPrice sdk.Dec, +) Params { return Params{ NoBaseFee: noBaseFee, BaseFeeChangeDenominator: baseFeeChangeDenom, ElasticityMultiplier: elasticityMultiplier, BaseFee: sdk.NewIntFromUint64(baseFee), EnableHeight: enableHeight, + MinGasPrice: minGasPrice, } } @@ -43,6 +52,7 @@ func DefaultParams() Params { ElasticityMultiplier: params.ElasticityMultiplier, BaseFee: sdk.NewIntFromUint64(params.InitialBaseFee), EnableHeight: 0, + MinGasPrice: sdk.ZeroDec(), } } @@ -54,6 +64,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(ParamStoreKeyElasticityMultiplier, &p.ElasticityMultiplier, validateElasticityMultiplier), paramtypes.NewParamSetPair(ParamStoreKeyBaseFee, &p.BaseFee, validateBaseFee), paramtypes.NewParamSetPair(ParamStoreKeyEnableHeight, &p.EnableHeight, validateEnableHeight), + paramtypes.NewParamSetPair(ParamStoreKeyMinGasPrice, &p.MinGasPrice, validateMinGasPrice), } } @@ -71,7 +82,7 @@ func (p Params) Validate() error { return fmt.Errorf("enable height cannot be negative: %d", p.EnableHeight) } - return nil + return validateMinGasPrice(p.MinGasPrice) } func (p *Params) IsBaseFeeEnabled(height int64) bool { @@ -132,3 +143,21 @@ func validateEnableHeight(i interface{}) error { return nil } + +func validateMinGasPrice(i interface{}) error { + v, ok := i.(sdk.Dec) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v.IsNil() { + return fmt.Errorf("invalid parameter: nil") + } + + if v.IsNegative() { + return fmt.Errorf("value cannot be negative: %s", i) + } + + return nil +} diff --git a/x/feemarket/types/params_test.go b/x/feemarket/types/params_test.go index 64d6e97e8f..9c8c8eba87 100644 --- a/x/feemarket/types/params_test.go +++ b/x/feemarket/types/params_test.go @@ -1,9 +1,10 @@ package types import ( - sdk "github.com/cosmos/cosmos-sdk/types" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/stretchr/testify/suite" ) @@ -29,7 +30,7 @@ func (suite *ParamsTestSuite) TestParamsValidate() { {"default", DefaultParams(), false}, { "valid", - NewParams(true, 7, 3, 2000000000, int64(544435345345435345)), + NewParams(true, 7, 3, 2000000000, int64(544435345345435345), sdk.NewDecWithPrec(20, 4)), false, }, { @@ -39,7 +40,12 @@ func (suite *ParamsTestSuite) TestParamsValidate() { }, { "base fee change denominator is 0 ", - NewParams(true, 0, 3, 2000000000, int64(544435345345435345)), + NewParams(true, 0, 3, 2000000000, int64(544435345345435345), sdk.NewDecWithPrec(20, 4)), + true, + }, + { + "invalid: min gas price negative", + NewParams(true, 7, 3, 2000000000, int64(544435345345435345), sdk.NewDecFromInt(sdk.NewInt(-1))), true, }, } @@ -71,3 +77,30 @@ func (suite *ParamsTestSuite) TestParamsValidatePriv() { suite.Require().Error(validateEnableHeight(int64(-544435345345435345))) suite.Require().NoError(validateEnableHeight(int64(544435345345435345))) } + +func (suite *ParamsTestSuite) TestParamsValidateMinGasPrice() { + testCases := []struct { + name string + value interface{} + expError bool + }{ + {"default", DefaultParams().MinGasPrice, false}, + {"valid", sdk.NewDecFromInt(sdk.NewInt(1)), false}, + {"invalid - wrong type - bool", false, true}, + {"invalid - wrong type - string", "", true}, + {"invalid - wrong type - int64", int64(123), true}, + {"invalid - wrong type - sdk.Int", sdk.NewInt(1), true}, + {"invalid - is nil", nil, true}, + {"invalid - is negative", sdk.NewDecFromInt(sdk.NewInt(-1)), true}, + } + + for _, tc := range testCases { + err := validateMinGasPrice(tc.value) + + if tc.expError { + suite.Require().Error(err, tc.name) + } else { + suite.Require().NoError(err, tc.name) + } + } +}