From 1076307e6b1a8c1e812e54a55811e21c3b6e1e7b Mon Sep 17 00:00:00 2001 From: JayT106 Date: Tue, 19 Oct 2021 04:49:29 -0400 Subject: [PATCH] tests: DynamicFeeTx (#649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test DynamicFeeTx against state_transition_benchmark_test * add feemarketGenesis in the app setup param * add dynamicTxFee flag to KeeperTestSuite * add feemarketGenesis.BaseFee setup * update TestAddLog * fix gasFeeCap assignment in newMsgEthereumTx * modify keeperTestSuite helper functions to support dynamicTxFee * update test cases in grpc_query_test w/ dynamicTxFee * update the evm keeper utils tests * add dynamic tx fee in the ante tests * remove duplicate type define * fix error return type * update changelog Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- CHANGELOG.md | 1 + app/ante/ante_test.go | 244 +++++++++++++++++ app/ante/utils_test.go | 25 +- app/test_helpers.go | 13 +- x/evm/handler_test.go | 13 +- x/evm/keeper/grpc_query_test.go | 82 +++++- x/evm/keeper/keeper_test.go | 92 +++++-- .../keeper/state_transition_benchmark_test.go | 92 ++++++- x/evm/keeper/statedb_test.go | 46 ++++ x/evm/keeper/utils.go | 7 +- x/evm/keeper/utils_test.go | 245 +++++++++++++++--- x/evm/types/msg.go | 2 +- 12 files changed, 780 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8832269..0bd99dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (rpc, test) [tharsis#608](https://github.com/tharsis/ethermint/pull/608) Fix rpc test. * (rpc) [tharsis#661](https://github.com/tharsis/ethermint/pull/661) Fix OOM bug when creating too many filters using JSON-RPC. * (evm) [tharsis#660](https://github.com/tharsis/ethermint/pull/660) Fix `nil` pointer panic in `ApplyNativeMessage`. +* (evm, test) [tharsis#649](https://github.com/tharsis/ethermint/pull/649) Test DynamicFeeTx. ## [v0.7.0] - 2021-10-07 diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index 491a4121..0ae08bf9 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -6,11 +6,16 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/types" + ethparams "github.com/ethereum/go-ethereum/params" "github.com/tharsis/ethermint/tests" evmtypes "github.com/tharsis/ethermint/x/evm/types" ) func (suite AnteTestSuite) TestAnteHandler() { + suite.dynamicTxFee = false + suite.SetupTest() // reset + addr, privKey := tests.NewAddrKey() to := tests.GenerateAddress() @@ -145,3 +150,242 @@ func (suite AnteTestSuite) TestAnteHandler() { }) } } + +func (suite AnteTestSuite) TestAnteHandlerWithDynamicTxFee() { + suite.dynamicTxFee = true + suite.SetupTest() // reset + + addr, privKey := tests.NewAddrKey() + to := tests.GenerateAddress() + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.Require().NoError(acc.SetSequence(1)) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + suite.app.EvmKeeper.AddBalance(addr, big.NewInt((ethparams.InitialBaseFee+10)*100000)) + + testCases := []struct { + name string + txFn func() sdk.Tx + checkTx bool + reCheckTx bool + expPass bool + }{ + { + "success - DeliverTx (contract)", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, true) + return tx + }, + false, false, true, + }, + { + "success - CheckTx (contract)", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, true) + return tx + }, + true, false, true, + }, + { + "success - ReCheckTx (contract)", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, true) + return tx + }, + false, true, true, + }, + { + "success - DeliverTx", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, true) + return tx + }, + false, false, true, + }, + { + "success - CheckTx", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 2, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, true) + return tx + }, + true, false, true, + }, + { + "success - ReCheckTx", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 3, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, true) + return tx + }, false, true, true, + }, + { + "success - CheckTx (cosmos tx not signed)", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 4, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, false, true, true, + }, + { + "fail - CheckTx (cosmos tx is not valid)", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 4, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + // bigger than MaxGasWanted + txBuilder.SetGasLimit(uint64(1 << 63)) + return txBuilder.GetTx() + }, false, true, false, + }, + { + "fail - CheckTx (memo too long)", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 5, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, true) + txBuilder.SetMemo(strings.Repeat("*", 257)) + return txBuilder.GetTx() + }, true, false, false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.ctx = suite.ctx.WithIsCheckTx(tc.reCheckTx).WithIsReCheckTx(tc.reCheckTx) + _, err := suite.anteHandler(suite.ctx, tc.txFn(), false) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + suite.dynamicTxFee = false +} diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go index 7757b72f..81aa4db4 100644 --- a/app/ante/utils_test.go +++ b/app/ante/utils_test.go @@ -24,6 +24,7 @@ import ( "github.com/tharsis/ethermint/encoding" "github.com/tharsis/ethermint/tests" evmtypes "github.com/tharsis/ethermint/x/evm/types" + feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -31,16 +32,28 @@ import ( type AnteTestSuite struct { suite.Suite - ctx sdk.Context - app *app.EthermintApp - clientCtx client.Context - anteHandler sdk.AnteHandler - ethSigner ethtypes.Signer + ctx sdk.Context + app *app.EthermintApp + clientCtx client.Context + anteHandler sdk.AnteHandler + ethSigner ethtypes.Signer + dynamicTxFee bool } func (suite *AnteTestSuite) SetupTest() { checkTx := false - suite.app = app.Setup(checkTx) + + if suite.dynamicTxFee { + // setup feemarketGenesis params + feemarketGenesis := feemarkettypes.DefaultGenesisState() + feemarketGenesis.Params.EnableHeight = 1 + feemarketGenesis.Params.NoBaseFee = false + feemarketGenesis.BaseFee = sdk.NewInt(feemarketGenesis.Params.InitialBaseFee) + suite.app = app.Setup(checkTx, feemarketGenesis) + } else { + suite.app = app.Setup(checkTx, nil) + } + suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 2, ChainID: "ethermint_9000-1", Time: time.Now().UTC()}) suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(evmtypes.DefaultEVMDenom, sdk.OneInt()))) suite.ctx = suite.ctx.WithBlockGasMeter(sdk.NewGasMeter(1000000000000000000)) diff --git a/app/test_helpers.go b/app/test_helpers.go index 6b80157a..179f227f 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -12,6 +12,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" + feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" ) // DefaultConsensusParams defines the default Tendermint consensus params used in @@ -34,12 +35,22 @@ var DefaultConsensusParams = &abci.ConsensusParams{ } // Setup initializes a new EthermintApp. A Nop logger is set in EthermintApp. -func Setup(isCheckTx bool) *EthermintApp { +func Setup(isCheckTx bool, feemarketGenesis *feemarkettypes.GenesisState) *EthermintApp { db := dbm.NewMemDB() app := NewEthermintApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, 5, encoding.MakeConfig(ModuleBasics), simapp.EmptyAppOptions{}) if !isCheckTx { // init chain must be called to stop deliverState from being nil genesisState := NewDefaultGenesisState() + + // Verify feeMarket genesis + if feemarketGenesis != nil { + if err := feemarketGenesis.Validate(); err != nil { + panic(err) + } + + genesisState[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) + } + stateBytes, err := json.MarshalIndent(genesisState, "", " ") if err != nil { panic(err) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 409dc62c..59820770 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/common" @@ -53,6 +54,8 @@ type EvmTestSuite struct { ethSigner ethtypes.Signer from common.Address to sdk.AccAddress + + dynamicTxFee bool } /// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`. @@ -70,7 +73,15 @@ func (suite *EvmTestSuite) DoSetupTest(t require.TestingT) { require.NoError(t, err) consAddress := sdk.ConsAddress(priv.PubKey().Address()) - suite.app = app.Setup(checkTx) + if suite.dynamicTxFee { + feemarketGenesis := feemarkettypes.DefaultGenesisState() + feemarketGenesis.Params.EnableHeight = 1 + feemarketGenesis.Params.NoBaseFee = false + suite.app = app.Setup(checkTx, feemarketGenesis) + } else { + suite.app = app.Setup(checkTx, nil) + } + coins := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdk.NewInt(100000000000000))) genesisState := app.ModuleBasics.DefaultGenesis(suite.app.AppCodec()) b32address := sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), priv.PubKey().Address().Bytes()) diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index e41a7ed5..716e2938 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -485,32 +485,33 @@ func (suite *KeeperTestSuite) TestEstimateGas() { gasCap uint64 ) testCases := []struct { - msg string - malleate func() - expPass bool - expGas uint64 + msg string + malleate func() + expPass bool + expGas uint64 + dynamicTxFee bool }{ // should success, because transfer value is zero {"default args", func() { args = types.TransactionArgs{To: &common.Address{}} - }, true, 21000}, + }, true, 21000, false}, // should fail, because the default From address(zero address) don't have fund {"not enough balance", func() { args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))} - }, false, 0}, + }, false, 0, false}, // should success, enough balance now {"enough balance", func() { args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))} - }, false, 0}, + }, false, 0, false}, // should success, because gas limit lower than 21000 is ignored {"gas exceed allowance", func() { args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper} - }, true, 21000}, + }, true, 21000, false}, // should fail, invalid gas cap {"gas exceed global allowance", func() { args = types.TransactionArgs{To: &common.Address{}} gasCap = 20000 - }, false, 0}, + }, false, 0, false}, // estimate gas of an erc20 contract deployment, the exact gas number is checked with geth {"contract deployment", func() { ctorArgs, err := ContractABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) @@ -520,7 +521,7 @@ func (suite *KeeperTestSuite) TestEstimateGas() { From: &suite.address, Data: (*hexutil.Bytes)(&data), } - }, true, 1186778}, + }, true, 1186778, false}, // estimate gas of an erc20 transfer, the exact gas number is checked with geth {"erc20 transfer", func() { contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) @@ -528,11 +529,46 @@ func (suite *KeeperTestSuite) TestEstimateGas() { transferData, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) suite.Require().NoError(err) args = types.TransactionArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)} - }, true, 51880}, + }, true, 51880, false}, + + // repeated tests with dynamicTxFee + {"default args w/ dynamicTxFee", func() { + args = types.TransactionArgs{To: &common.Address{}} + }, true, 21000, true}, + {"not enough balance w/ dynamicTxFee", func() { + args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))} + }, false, 0, true}, + {"enough balance w/ dynamicTxFee", func() { + args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))} + }, false, 0, true}, + {"gas exceed allowance w/ dynamicTxFee", func() { + args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper} + }, true, 21000, true}, + {"gas exceed global allowance w/ dynamicTxFee", func() { + args = types.TransactionArgs{To: &common.Address{}} + gasCap = 20000 + }, false, 0, true}, + {"contract deployment w/ dynamicTxFee", func() { + ctorArgs, err := ContractABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) + suite.Require().NoError(err) + data := append(ContractBin, ctorArgs...) + args = types.TransactionArgs{ + From: &suite.address, + Data: (*hexutil.Bytes)(&data), + } + }, true, 1186778, true}, + {"erc20 transfer w/ dynamicTxFee", func() { + contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) + suite.Commit() + transferData, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000)) + suite.Require().NoError(err) + args = types.TransactionArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)} + }, true, 51880, true}, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.dynamicTxFee = tc.dynamicTxFee suite.SetupTest() gasCap = 25_000_000 tc.malleate() @@ -553,6 +589,7 @@ func (suite *KeeperTestSuite) TestEstimateGas() { } }) } + suite.dynamicTxFee = false // reset flag } func (suite *KeeperTestSuite) TestTraceTx() { @@ -568,6 +605,7 @@ func (suite *KeeperTestSuite) TestTraceTx() { malleate func() expPass bool traceResponse []byte + dynamicTxFee bool }{ { msg: "default trace", @@ -586,10 +624,30 @@ func (suite *KeeperTestSuite) TestTraceTx() { expPass: true, traceResponse: []byte{0x5b, 0x5d}, }, + { + msg: "default trace with dynamicTxFee", + malleate: func() { + traceConfig = nil + }, + expPass: true, + traceResponse: []byte{0x7b, 0x22, 0x67, 0x61, 0x73, 0x22, 0x3a, 0x33, 0x34, 0x38, 0x32, 0x38, 0x2c, 0x22, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x22, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d}, + dynamicTxFee: true, + }, { + msg: "javascript tracer with dynamicTxFee", + malleate: func() { + traceConfig = &types.TraceConfig{ + Tracer: "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}", + } + }, + expPass: true, + traceResponse: []byte{0x5b, 0x5d}, + dynamicTxFee: true, + }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.dynamicTxFee = tc.dynamicTxFee suite.SetupTest() // Deploy contract contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt()) @@ -614,4 +672,6 @@ func (suite *KeeperTestSuite) TestTraceTx() { } }) } + + suite.dynamicTxFee = false // reset flag } diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 69fff145..0f164db1 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -17,6 +17,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" "github.com/tharsis/ethermint/app" "github.com/tharsis/ethermint/crypto/ethsecp256k1" @@ -79,6 +80,8 @@ type KeeperTestSuite struct { appCodec codec.Codec signer keyring.Signer + + dynamicTxFee bool } /// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`. @@ -96,7 +99,17 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { require.NoError(t, err) suite.consAddress = sdk.ConsAddress(priv.PubKey().Address()) - suite.app = app.Setup(checkTx) + if suite.dynamicTxFee { + // setup feemarketGenesis params + feemarketGenesis := feemarkettypes.DefaultGenesisState() + feemarketGenesis.Params.EnableHeight = 1 + feemarketGenesis.Params.NoBaseFee = false + feemarketGenesis.BaseFee = sdk.NewInt(feemarketGenesis.Params.InitialBaseFee) + suite.app = app.Setup(checkTx, feemarketGenesis) + } else { + suite.app = app.Setup(checkTx, nil) + } + suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{ Height: 1, ChainID: "ethermint_9000-1", @@ -197,16 +210,33 @@ func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner commo require.NoError(t, err) nonce := suite.app.EvmKeeper.GetNonce(suite.address) - erc20DeployTx := types.NewTxContract( - chainID, - nonce, - nil, // amount - res.Gas, // gasLimit - nil, // gasPrice - nil, nil, - data, // input - nil, // accesses - ) + + var erc20DeployTx *types.MsgEthereumTx + if suite.dynamicTxFee { + erc20DeployTx = types.NewTxContract( + chainID, + nonce, + nil, // amount + res.Gas, // gasLimit + nil, // gasPrice + suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx), + big.NewInt(1), + data, // input + ðtypes.AccessList{}, // accesses + ) + } else { + erc20DeployTx = types.NewTxContract( + chainID, + nonce, + nil, // amount + res.Gas, // gasLimit + nil, // gasPrice + nil, nil, + data, // input + nil, // accesses + ) + } + erc20DeployTx.From = suite.address.Hex() err = erc20DeployTx.Sign(ethtypes.LatestSignerForChainID(chainID), suite.signer) require.NoError(t, err) @@ -231,17 +261,35 @@ func (suite *KeeperTestSuite) TransferERC20Token(t require.TestingT, contractAdd require.NoError(t, err) nonce := suite.app.EvmKeeper.GetNonce(suite.address) - ercTransferTx := types.NewTx( - chainID, - nonce, - &contractAddr, - nil, - res.Gas, - nil, - nil, nil, - transferData, - nil, - ) + + var ercTransferTx *types.MsgEthereumTx + if suite.dynamicTxFee { + ercTransferTx = types.NewTx( + chainID, + nonce, + &contractAddr, + nil, + res.Gas, + nil, + suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx), + big.NewInt(1), + transferData, + ðtypes.AccessList{}, // accesses + ) + } else { + ercTransferTx = types.NewTx( + chainID, + nonce, + &contractAddr, + nil, + res.Gas, + nil, + nil, nil, + transferData, + nil, + ) + } + ercTransferTx.From = suite.address.Hex() err = ercTransferTx.Sign(ethtypes.LatestSignerForChainID(chainID), suite.signer) require.NoError(t, err) diff --git a/x/evm/keeper/state_transition_benchmark_test.go b/x/evm/keeper/state_transition_benchmark_test.go index 4d4feceb..1d2deb93 100644 --- a/x/evm/keeper/state_transition_benchmark_test.go +++ b/x/evm/keeper/state_transition_benchmark_test.go @@ -31,6 +31,15 @@ var templateLegacyTx = ðtypes.LegacyTx{ Data: []byte{}, } +var templateDynamicFeeTx = ðtypes.DynamicFeeTx{ + GasFeeCap: big.NewInt(10), + GasTipCap: big.NewInt(2), + Gas: 21000, + To: &common.Address{}, + Value: big.NewInt(0), + Data: []byte{}, +} + func newSignedEthTx( txData ethtypes.TxData, nonce uint64, @@ -46,6 +55,9 @@ func newSignedEthTx( case *ethtypes.LegacyTx: txData.Nonce = nonce ethTx = ethtypes.NewTx(txData) + case *ethtypes.DynamicFeeTx: + txData.Nonce = nonce + ethTx = ethtypes.NewTx(txData) default: return nil, errors.New("unknown transaction type!") } @@ -70,7 +82,7 @@ func newNativeMessage( cfg *params.ChainConfig, krSigner keyring.Signer, ethSigner ethtypes.Signer, - isLegacy bool, + txType byte, ) (core.Message, error) { msgSigner := ethtypes.MakeSigner(cfg, big.NewInt(blockHeight)) @@ -78,12 +90,20 @@ func newNativeMessage( ethTx *ethtypes.Transaction baseFee *big.Int ) - if isLegacy { + + switch txType { + case ethtypes.LegacyTxType: templateLegacyTx.Nonce = nonce ethTx = ethtypes.NewTx(templateLegacyTx) - } else { + case ethtypes.AccessListTxType: templateAccessListTx.Nonce = nonce ethTx = ethtypes.NewTx(templateAccessListTx) + case ethtypes.DynamicFeeTxType: + templateDynamicFeeTx.Nonce = nonce + ethTx = ethtypes.NewTx(templateDynamicFeeTx) + baseFee = big.NewInt(3) + default: + return nil, errors.New("unsupport tx type") } msg := &evmtypes.MsgEthereumTx{} @@ -94,7 +114,7 @@ func newNativeMessage( return nil, err } - m, err := msg.AsMessage(msgSigner, baseFee) // TODO: add DynamicFeeTx + m, err := msg.AsMessage(msgSigner, baseFee) if err != nil { return nil, err } @@ -156,6 +176,33 @@ func BenchmarkApplyTransactionWithLegacyTx(b *testing.B) { } } +func BenchmarkApplyTransactionWithDynamicFeeTx(b *testing.B) { + suite := KeeperTestSuite{dynamicTxFee: true} + suite.DoSetupTest(b) + + ethSigner := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + b.StopTimer() + tx, err := newSignedEthTx(templateDynamicFeeTx, + suite.app.EvmKeeper.GetNonce(suite.address), + sdk.AccAddress(suite.address.Bytes()), + suite.signer, + ethSigner, + ) + require.NoError(b, err) + + b.StartTimer() + resp, err := suite.app.EvmKeeper.ApplyTransaction(tx) + b.StopTimer() + + require.NoError(b, err) + require.False(b, resp.Failed()) + } +} + func BenchmarkApplyNativeMessage(b *testing.B) { suite := KeeperTestSuite{} suite.DoSetupTest(b) @@ -176,7 +223,7 @@ func BenchmarkApplyNativeMessage(b *testing.B) { ethCfg, suite.signer, signer, - false, + ethtypes.AccessListTxType, ) require.NoError(b, err) @@ -209,7 +256,40 @@ func BenchmarkApplyNativeMessageWithLegacyTx(b *testing.B) { ethCfg, suite.signer, signer, - true, + ethtypes.LegacyTxType, + ) + require.NoError(b, err) + + b.StartTimer() + resp, err := suite.app.EvmKeeper.ApplyNativeMessage(m) + b.StopTimer() + + require.NoError(b, err) + require.False(b, resp.Failed()) + } +} + +func BenchmarkApplyNativeMessageWithDynamicFeeTx(b *testing.B) { + suite := KeeperTestSuite{dynamicTxFee: true} + suite.DoSetupTest(b) + + params := suite.app.EvmKeeper.GetParams(suite.ctx) + ethCfg := params.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID()) + signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + b.StopTimer() + + m, err := newNativeMessage( + suite.app.EvmKeeper.GetNonce(suite.address), + suite.ctx.BlockHeight(), + suite.address, + ethCfg, + suite.signer, + signer, + ethtypes.DynamicFeeTxType, ) require.NoError(b, err) diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index cdac2e19..82a2966f 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -563,6 +563,20 @@ func (suite *KeeperTestSuite) TestAddLog() { msg2, _ = tx2.GetMsgs()[0].(*types.MsgEthereumTx) txHash2 := msg2.AsTransaction().Hash() + msg3 := types.NewTx(big.NewInt(1), 0, &suite.address, big.NewInt(1), 100000, nil, big.NewInt(1), big.NewInt(1), []byte("test"), nil) + msg3.From = addr.Hex() + + tx3 := suite.CreateTestTx(msg3, privKey) + msg3, _ = tx3.GetMsgs()[0].(*types.MsgEthereumTx) + txHash3 := msg3.AsTransaction().Hash() + + msg4 := types.NewTx(big.NewInt(1), 1, &suite.address, big.NewInt(1), 100000, nil, big.NewInt(1), big.NewInt(1), []byte("test"), nil) + msg4.From = addr.Hex() + + tx4 := suite.CreateTestTx(msg4, privKey) + msg4, _ = tx4.GetMsgs()[0].(*types.MsgEthereumTx) + txHash4 := msg4.AsTransaction().Hash() + testCases := []struct { name string hash common.Hash @@ -601,6 +615,38 @@ func (suite *KeeperTestSuite) TestAddLog() { suite.app.EvmKeeper.IncreaseTxIndexTransient() }, }, + { + "dynamicfee tx hash from message", + txHash3, + ðtypes.Log{ + Address: addr, + }, + ðtypes.Log{ + Address: addr, + TxHash: txHash3, + }, + func() {}, + }, + { + "log index keep increasing in new dynamicfee tx", + txHash4, + ðtypes.Log{ + Address: addr, + }, + ðtypes.Log{ + Address: addr, + TxHash: txHash4, + TxIndex: 1, + Index: 1, + }, + func() { + suite.app.EvmKeeper.SetTxHashTransient(txHash) + suite.app.EvmKeeper.AddLog(ðtypes.Log{ + Address: addr, + }) + suite.app.EvmKeeper.IncreaseTxIndexTransient() + }, + }, } for _, tc := range testCases { diff --git a/x/evm/keeper/utils.go b/x/evm/keeper/utils.go index 9ff014c2..44c70466 100644 --- a/x/evm/keeper/utils.go +++ b/x/evm/keeper/utils.go @@ -62,7 +62,12 @@ func (k Keeper) DeductTxCostsFromUserBalance( if london && !feeMktParams.NoBaseFee && txData.TxType() == ethtypes.DynamicFeeTxType { baseFee := k.feeMarketKeeper.GetBaseFee(ctx) - effectiveTip = cmath.BigMin(txData.GetGasTipCap(), new(big.Int).Sub(txData.GetGasFeeCap(), baseFee)) + gasFeeGap := new(big.Int).Sub(txData.GetGasFeeCap(), baseFee) + if gasFeeGap.Sign() == -1 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) ", txData.GetGasFeeCap(), baseFee) + } + + effectiveTip = cmath.BigMin(txData.GetGasTipCap(), gasFeeGap) } gasUsed := new(big.Int).SetUint64(txData.GetGas()) diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index d799cbe6..6a3ba463 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -5,7 +5,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + cmath "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" + ethparams "github.com/ethereum/go-ethereum/params" evmkeeper "github.com/tharsis/ethermint/x/evm/keeper" evmtypes "github.com/tharsis/ethermint/x/evm/types" ) @@ -19,14 +21,17 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() { negInt := sdk.NewInt(-10) testCases := []struct { - name string - to string - gasLimit uint64 - gasPrice *sdk.Int - cost *sdk.Int - from string - accessList *ethtypes.AccessList - expectPass bool + name string + to string + gasLimit uint64 + gasPrice *sdk.Int + gasFeeCap *big.Int + gasTipCap *big.Int + cost *sdk.Int + from string + accessList *ethtypes.AccessList + expectPass bool + dynamicTxFee bool }{ { name: "Enough balance", @@ -108,6 +113,94 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() { accessList: ðtypes.AccessList{}, expectPass: false, }, + { + name: "Enough balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 10, + gasFeeCap: big.NewInt(1), + cost: &oneInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: true, + dynamicTxFee: true, + }, + { + name: "Equal balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 99, + gasFeeCap: big.NewInt(1), + cost: &oneInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: true, + dynamicTxFee: true, + }, + { + name: "negative cost w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 1, + gasFeeCap: big.NewInt(1), + cost: &negInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: false, + dynamicTxFee: true, + }, + { + name: "Higher gas limit, not enough balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 100, + gasFeeCap: big.NewInt(1), + cost: &oneInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: false, + dynamicTxFee: true, + }, + { + name: "Higher gas price, enough balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 10, + gasFeeCap: big.NewInt(5), + cost: &oneInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: true, + dynamicTxFee: true, + }, + { + name: "Higher gas price, not enough balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 20, + gasFeeCap: big.NewInt(5), + cost: &oneInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: false, + dynamicTxFee: true, + }, + { + name: "Higher cost, enough balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 10, + gasFeeCap: big.NewInt(5), + cost: &fiftyInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: true, + dynamicTxFee: true, + }, + { + name: "Higher cost, not enough balance w/ dynamicTxFee", + to: suite.address.String(), + gasLimit: 10, + gasFeeCap: big.NewInt(5), + cost: &hundredInt, + from: suite.address.String(), + accessList: ðtypes.AccessList{}, + expectPass: false, + dynamicTxFee: true, + }, } suite.app.EvmKeeper.AddBalance(suite.address, hundredInt.BigInt()) @@ -118,15 +211,25 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() { suite.Run(tc.name, func() { to := common.HexToAddress(tc.from) - var amount, gasPrice *big.Int + var amount, gasPrice, gasFeeCap, gasTipCap *big.Int if tc.cost != nil { amount = tc.cost.BigInt() } - if tc.gasPrice != nil { - gasPrice = tc.gasPrice.BigInt() + + if tc.dynamicTxFee { + gasFeeCap = tc.gasFeeCap + if tc.gasTipCap == nil { + gasTipCap = oneInt.BigInt() + } else { + gasTipCap = tc.gasTipCap + } + } else { + if tc.gasPrice != nil { + gasPrice = tc.gasPrice.BigInt() + } } - tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &to, amount, tc.gasLimit, gasPrice, nil, nil, nil, tc.accessList) + tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &to, amount, tc.gasLimit, gasPrice, gasFeeCap, gasTipCap, nil, tc.accessList) tx.From = tc.from txData, _ := evmtypes.UnpackTxData(tx.Data) @@ -154,14 +257,18 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { oneInt := sdk.NewInt(1) fiveInt := sdk.NewInt(5) fiftyInt := sdk.NewInt(50) + hundredBaseFeeInt := sdk.NewInt(ethparams.InitialBaseFee * 100) testCases := []struct { - name string - gasLimit uint64 - gasPrice *sdk.Int - cost *sdk.Int - accessList *ethtypes.AccessList - expectPass bool + name string + gasLimit uint64 + gasPrice *sdk.Int + gasFeeCap *big.Int + gasTipCap *big.Int + cost *sdk.Int + accessList *ethtypes.AccessList + expectPass bool + dynamicTxFee bool }{ { name: "Enough balance", @@ -213,24 +320,82 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { accessList: ðtypes.AccessList{}, expectPass: true, }, + // testcases with dynamicTxFee enabled. + { + name: "Invalid gasFeeCap w/ dynamicTxFee", + gasLimit: 10, + gasFeeCap: big.NewInt(1), + gasTipCap: big.NewInt(1), + cost: &oneInt, + accessList: ðtypes.AccessList{}, + expectPass: false, + dynamicTxFee: true, + }, + // TODO: is this case valid? + { + name: "empty fee failed to deduct", + gasLimit: 10, + gasFeeCap: big.NewInt(ethparams.InitialBaseFee), + gasTipCap: big.NewInt(1), + cost: &oneInt, + accessList: ðtypes.AccessList{}, + expectPass: false, + dynamicTxFee: true, + }, + { + name: "effectiveTip equal to gasTipCap", + gasLimit: 100, + gasFeeCap: big.NewInt(ethparams.InitialBaseFee + 2), + cost: &oneInt, + accessList: ðtypes.AccessList{}, + expectPass: true, + dynamicTxFee: true, + }, + { + name: "effectiveTip equal to (gasFeeCap - baseFee)", + gasLimit: 105, + gasFeeCap: big.NewInt(ethparams.InitialBaseFee + 1), + gasTipCap: big.NewInt(2), + cost: &oneInt, + accessList: ðtypes.AccessList{}, + expectPass: true, + dynamicTxFee: true, + }, } for i, tc := range testCases { suite.Run(tc.name, func() { + suite.dynamicTxFee = tc.dynamicTxFee suite.SetupTest() - suite.app.EvmKeeper.AddBalance(suite.address, hundredInt.BigInt()) - balance := suite.app.EvmKeeper.GetBalance(suite.address) - suite.Require().Equal(balance, hundredInt.BigInt()) - var amount, gasPrice *big.Int + var amount, gasPrice, gasFeeCap, gasTipCap *big.Int if tc.cost != nil { amount = tc.cost.BigInt() } - if tc.gasPrice != nil { - gasPrice = tc.gasPrice.BigInt() + + if suite.dynamicTxFee { + if tc.gasFeeCap != nil { + gasFeeCap = tc.gasFeeCap + } + if tc.gasTipCap == nil { + gasTipCap = oneInt.BigInt() + } else { + gasTipCap = tc.gasTipCap + } + suite.app.EvmKeeper.AddBalance(suite.address, hundredBaseFeeInt.BigInt()) + balance := suite.app.EvmKeeper.GetBalance(suite.address) + suite.Require().Equal(balance, hundredBaseFeeInt.BigInt()) + } else { + if tc.gasPrice != nil { + gasPrice = tc.gasPrice.BigInt() + } + + suite.app.EvmKeeper.AddBalance(suite.address, hundredInt.BigInt()) + balance := suite.app.EvmKeeper.GetBalance(suite.address) + suite.Require().Equal(balance, hundredInt.BigInt()) } - tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &suite.address, amount, tc.gasLimit, gasPrice, nil, nil, nil, tc.accessList) + tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &suite.address, amount, tc.gasLimit, gasPrice, gasFeeCap, gasTipCap, nil, tc.accessList) tx.From = suite.address.String() txData, _ := evmtypes.UnpackTxData(tx.Data) @@ -242,23 +407,37 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { evmtypes.DefaultEVMDenom, false, false, - false, // london + suite.dynamicTxFee, // london ) if tc.expectPass { suite.Require().NoError(err, "valid test %d failed", i) - suite.Require().Equal( - fees, - sdk.NewCoins( - sdk.NewCoin(evmtypes.DefaultEVMDenom, tc.gasPrice.Mul(sdk.NewIntFromUint64(tc.gasLimit))), - ), - "valid test %d failed, fee value is wrong ", i, - ) + if tc.dynamicTxFee { + baseFee := suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx) + gasFeeGap := new(big.Int).Sub(txData.GetGasFeeCap(), baseFee) + effectiveTip := cmath.BigMin(txData.GetGasTipCap(), gasFeeGap) + suite.Require().Equal( + fees, + sdk.NewCoins( + sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewIntFromBigInt(effectiveTip).Mul(sdk.NewIntFromUint64(tc.gasLimit))), + ), + "valid test %d failed, fee value is wrong ", i, + ) + } else { + suite.Require().Equal( + fees, + sdk.NewCoins( + sdk.NewCoin(evmtypes.DefaultEVMDenom, tc.gasPrice.Mul(sdk.NewIntFromUint64(tc.gasLimit))), + ), + "valid test %d failed, fee value is wrong ", i, + ) + } } else { suite.Require().Error(err, "invalid test %d passed", i) suite.Require().Nil(fees, "invalid test %d passed. fees value must be nil", i) } }) } + suite.dynamicTxFee = false // reset flag } diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index ea393004..aa61cf07 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -94,7 +94,7 @@ func newMsgEthereumTx( } case accesses != nil && gasFeeCap != nil && gasTipCap != nil: gtc := sdk.NewIntFromBigInt(gasTipCap) - gfc := sdk.NewIntFromBigInt(gasTipCap) + gfc := sdk.NewIntFromBigInt(gasFeeCap) txData = &DynamicFeeTx{ ChainID: cid,