diff --git a/CHANGELOG.md b/CHANGELOG.md index be40b4d9..7fbfa9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (cli) [#1360](https://github.com/evmos/ethermint/pull/1360) Introduce a new `grpc-only` flag, such that when enabled, will start the node in a query-only mode. Note, gRPC MUST be enabled with this flag. * (rpc) [#1378](https://github.com/evmos/ethermint/pull/1378) Add support for EVM RPC metrics * (ante) [#1390](https://github.com/evmos/ethermint/pull/1390) Added multisig tx support. +* (test) [#1396](https://github.com/evmos/ethermint/pull/1396) Increase test coverage for the EVM module `keeper` ### Bug Fixes diff --git a/x/evm/keeper/abci_test.go b/x/evm/keeper/abci_test.go new file mode 100644 index 00000000..dc7c265c --- /dev/null +++ b/x/evm/keeper/abci_test.go @@ -0,0 +1,18 @@ +package keeper_test + +import ( + evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/tendermint/tendermint/abci/types" +) + +func (suite *KeeperTestSuite) TestEndBlock() { + em := suite.ctx.EventManager() + suite.Require().Equal(0, len(em.Events())) + + res := suite.app.EvmKeeper.EndBlock(suite.ctx, types.RequestEndBlock{}) + suite.Require().Equal([]types.ValidatorUpdate{}, res) + + // should emit 1 EventTypeBlockBloom event on EndBlock + suite.Require().Equal(1, len(em.Events())) + suite.Require().Equal(evmtypes.EventTypeBlockBloom, em.Events()[0].Type) +} diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 1152c2e4..56f055a6 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -13,11 +13,11 @@ import ( "github.com/ethereum/go-ethereum/crypto" ethlogger "github.com/ethereum/go-ethereum/eth/tracers/logger" ethparams "github.com/ethereum/go-ethereum/params" + "github.com/evmos/ethermint/tests" "github.com/evmos/ethermint/x/evm/statedb" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/evmos/ethermint/crypto/ethsecp256k1" "github.com/evmos/ethermint/server/config" ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm/types" @@ -491,9 +491,11 @@ func (suite *KeeperTestSuite) TestQueryValidatorAccount() { func (suite *KeeperTestSuite) TestEstimateGas() { gasHelper := hexutil.Uint64(20000) + higherGas := hexutil.Uint64(25000) + hexBigInt := hexutil.Big(*big.NewInt(1)) var ( - args types.TransactionArgs + args interface{} gasCap uint64 ) testCases := []struct { @@ -504,78 +506,223 @@ func (suite *KeeperTestSuite) TestEstimateGas() { enableFeemarket bool }{ // should success, because transfer value is zero - {"default args", func() { - args = types.TransactionArgs{To: &common.Address{}} - }, true, 21000, false}, + { + "default args - special case for ErrIntrinsicGas on contract creation, raise gas limit", + func() { + args = types.TransactionArgs{} + }, + true, + ethparams.TxGasContractCreation, + false, + }, + // should success, because transfer value is zero + { + "default args with 'to' address", + func() { + args = types.TransactionArgs{To: &common.Address{}} + }, + true, + ethparams.TxGas, + 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}, + { + "not enough balance", + func() { + args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))} + }, + 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}, + { + "enough balance", + func() { + args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))} + }, 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, false}, + { + "gas exceed allowance", + func() { + args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper} + }, + true, + ethparams.TxGas, + false, + }, // should fail, invalid gas cap - {"gas exceed global allowance", func() { - args = types.TransactionArgs{To: &common.Address{}} - gasCap = 20000 - }, false, 0, false}, + { + "gas exceed global allowance", + func() { + args = types.TransactionArgs{To: &common.Address{}} + gasCap = 20000 + }, + false, + 0, + false, + }, // estimate gas of an erc20 contract deployment, the exact gas number is checked with geth - {"contract deployment", func() { - ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) - suite.Require().NoError(err) - data := append(types.ERC20Contract.Bin, ctorArgs...) - args = types.TransactionArgs{ - From: &suite.address, - Data: (*hexutil.Bytes)(&data), - } - }, true, 1186778, false}, + { + "contract deployment", + func() { + ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) + suite.Require().NoError(err) + data := append(types.ERC20Contract.Bin, ctorArgs...) + args = types.TransactionArgs{ + From: &suite.address, + Data: (*hexutil.Bytes)(&data), + } + }, + 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, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) - suite.Commit() - transferData, err := types.ERC20Contract.ABI.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, false}, - + { + "erc20 transfer", + func() { + contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) + suite.Commit() + transferData, err := types.ERC20Contract.ABI.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, + false, + }, // repeated tests with enableFeemarket - {"default args w/ enableFeemarket", func() { - args = types.TransactionArgs{To: &common.Address{}} - }, true, 21000, true}, - {"not enough balance w/ enableFeemarket", func() { - args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))} - }, false, 0, true}, - {"enough balance w/ enableFeemarket", func() { - args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))} - }, false, 0, true}, - {"gas exceed allowance w/ enableFeemarket", func() { - args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper} - }, true, 21000, true}, - {"gas exceed global allowance w/ enableFeemarket", func() { - args = types.TransactionArgs{To: &common.Address{}} - gasCap = 20000 - }, false, 0, true}, - {"contract deployment w/ enableFeemarket", func() { - ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) - suite.Require().NoError(err) - data := append(types.ERC20Contract.Bin, ctorArgs...) - args = types.TransactionArgs{ - From: &suite.address, - Data: (*hexutil.Bytes)(&data), - } - }, true, 1186778, true}, - {"erc20 transfer w/ enableFeemarket", func() { - contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) - suite.Commit() - transferData, err := types.ERC20Contract.ABI.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}, + { + "default args w/ enableFeemarket", + func() { + args = types.TransactionArgs{To: &common.Address{}} + }, + true, + ethparams.TxGas, + true, + }, + { + "not enough balance w/ enableFeemarket", + func() { + args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))} + }, + false, + 0, + true, + }, + { + "enough balance w/ enableFeemarket", + func() { + args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))} + }, + false, + 0, + true, + }, + { + "gas exceed allowance w/ enableFeemarket", + func() { + args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper} + }, + true, + ethparams.TxGas, + true, + }, + { + "gas exceed global allowance w/ enableFeemarket", + func() { + args = types.TransactionArgs{To: &common.Address{}} + gasCap = 20000 + }, + false, + 0, + true, + }, + { + "contract deployment w/ enableFeemarket", + func() { + ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) + suite.Require().NoError(err) + data := append(types.ERC20Contract.Bin, ctorArgs...) + args = types.TransactionArgs{ + From: &suite.address, + Data: (*hexutil.Bytes)(&data), + } + }, + true, + 1186778, + true, + }, + { + "erc20 transfer w/ enableFeemarket", + func() { + contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) + suite.Commit() + transferData, err := types.ERC20Contract.ABI.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, + }, + { + "contract creation but 'create' param disabled", + func() { + ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) + suite.Require().NoError(err) + data := append(types.ERC20Contract.Bin, ctorArgs...) + args = types.TransactionArgs{ + From: &suite.address, + Data: (*hexutil.Bytes)(&data), + } + params := suite.app.EvmKeeper.GetParams(suite.ctx) + params.EnableCreate = false + suite.app.EvmKeeper.SetParams(suite.ctx, params) + }, + false, + 0, + false, + }, + { + "specified gas in args higher than ethparams.TxGas (21,000)", + func() { + args = types.TransactionArgs{ + To: &common.Address{}, + Gas: &higherGas, + } + }, + true, + ethparams.TxGas, + false, + }, + { + "specified gas in args higher than request gasCap", + func() { + gasCap = 22_000 + args = types.TransactionArgs{ + To: &common.Address{}, + Gas: &higherGas, + } + }, + true, + ethparams.TxGas, + false, + }, + { + "invalid args - specified both gasPrice and maxFeePerGas", + func() { + args = types.TransactionArgs{ + To: &common.Address{}, + GasPrice: &hexBigInt, + MaxFeePerGas: &hexBigInt, + } + }, + false, + 0, + false, + }, } for _, tc := range testCases { @@ -702,6 +849,88 @@ func (suite *KeeperTestSuite) TestTraceTx() { traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":", enableFeemarket: false, }, + { + msg: "invalid trace config - Negative Limit", + malleate: func() { + traceConfig = &types.TraceConfig{ + DisableStack: true, + DisableStorage: true, + EnableMemory: false, + Limit: -1, + } + }, + expPass: false, + }, + { + msg: "invalid trace config - Invalid Tracer", + malleate: func() { + traceConfig = &types.TraceConfig{ + DisableStack: true, + DisableStorage: true, + EnableMemory: false, + Tracer: "invalid_tracer", + } + }, + expPass: false, + }, + { + msg: "invalid trace config - Invalid Timeout", + malleate: func() { + traceConfig = &types.TraceConfig{ + DisableStack: true, + DisableStorage: true, + EnableMemory: false, + Timeout: "wrong_time", + } + }, + expPass: false, + }, + { + msg: "trace config - Execution Timeout", + malleate: func() { + traceConfig = &types.TraceConfig{ + DisableStack: true, + DisableStorage: true, + EnableMemory: false, + Timeout: "0s", + } + }, + expPass: false, + }, + { + msg: "default tracer with contract creation tx as predecessor but 'create' param disabled", + malleate: func() { + traceConfig = nil + + // increase nonce to avoid address collision + vmdb := suite.StateDB() + vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1) + suite.Require().NoError(vmdb.Commit()) + + chainID := suite.app.EvmKeeper.ChainID() + nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address) + data := types.ERC20Contract.Bin + contractTx := types.NewTxContract( + chainID, + nonce, + nil, // amount + ethparams.TxGasContractCreation, // gasLimit + nil, // gasPrice + nil, nil, + data, // input + nil, // accesses + ) + + predecessors = append(predecessors, contractTx) + suite.Commit() + + params := suite.app.EvmKeeper.GetParams(suite.ctx) + params.EnableCreate = false + suite.app.EvmKeeper.SetParams(suite.ctx, params) + }, + expPass: true, + traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":", + }, } for _, tc := range testCases { @@ -722,7 +951,6 @@ func (suite *KeeperTestSuite) TestTraceTx() { Predecessors: predecessors, } res, err := suite.queryClient.TraceTx(sdk.WrapSDKContext(suite.ctx), &traceReq) - suite.Require().NoError(err) if tc.expPass { suite.Require().NoError(err) @@ -836,6 +1064,31 @@ func (suite *KeeperTestSuite) TestTraceBlock() { traceResponse: "[{\"result\":{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PU", enableFeemarket: false, }, + { + msg: "invalid trace config - Negative Limit", + malleate: func() { + traceConfig = &types.TraceConfig{ + DisableStack: true, + DisableStorage: true, + EnableMemory: false, + Limit: -1, + } + }, + expPass: false, + }, + { + msg: "invalid trace config - Invalid Tracer", + malleate: func() { + traceConfig = &types.TraceConfig{ + DisableStack: true, + DisableStorage: true, + EnableMemory: false, + Tracer: "invalid_tracer", + } + }, + expPass: true, + traceResponse: "[]", + }, } for _, tc := range testCases { @@ -877,10 +1130,7 @@ func (suite *KeeperTestSuite) TestTraceBlock() { } func (suite *KeeperTestSuite) TestNonceInQuery() { - suite.SetupTest() - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) - address := common.BytesToAddress(priv.PubKey().Address().Bytes()) + address := tests.GenerateAddress() suite.Require().Equal(uint64(0), suite.app.EvmKeeper.GetNonce(suite.ctx, address)) supply := sdkmath.NewIntWithDecimal(1000, 18).BigInt() @@ -981,3 +1231,158 @@ func (suite *KeeperTestSuite) TestQueryBaseFee() { suite.enableFeemarket = false suite.enableLondonHF = true } + +func (suite *KeeperTestSuite) TestEthCall() { + var ( + req *types.EthCallRequest + ) + + address := tests.GenerateAddress() + suite.Require().Equal(uint64(0), suite.app.EvmKeeper.GetNonce(suite.ctx, address)) + supply := sdkmath.NewIntWithDecimal(1000, 18).BigInt() + + hexBigInt := hexutil.Big(*big.NewInt(1)) + ctorArgs, err := types.ERC20Contract.ABI.Pack("", address, supply) + suite.Require().NoError(err) + + data := append(types.ERC20Contract.Bin, ctorArgs...) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "invalid args", + func() { + req = &types.EthCallRequest{Args: []byte("invalid args"), GasCap: uint64(config.DefaultGasCap)} + }, + false, + }, + { + "invalid args - specified both gasPrice and maxFeePerGas", + func() { + args, err := json.Marshal(&types.TransactionArgs{ + From: &address, + Data: (*hexutil.Bytes)(&data), + GasPrice: &hexBigInt, + MaxFeePerGas: &hexBigInt, + }) + + suite.Require().NoError(err) + req = &types.EthCallRequest{Args: args, GasCap: uint64(config.DefaultGasCap)} + }, + false, + }, + { + "set param EnableCreate = false", + func() { + args, err := json.Marshal(&types.TransactionArgs{ + From: &address, + Data: (*hexutil.Bytes)(&data), + }) + + suite.Require().NoError(err) + req = &types.EthCallRequest{Args: args, GasCap: uint64(config.DefaultGasCap)} + + params := suite.app.EvmKeeper.GetParams(suite.ctx) + params.EnableCreate = false + suite.app.EvmKeeper.SetParams(suite.ctx, params) + }, + false, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + tc.malleate() + + res, err := suite.queryClient.EthCall(suite.ctx, req) + if tc.expPass { + suite.Require().NotNil(res) + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + +} + +func (suite *KeeperTestSuite) TestEmptyRequest() { + k := suite.app.EvmKeeper + + testCases := []struct { + name string + queryFunc func() (interface{}, error) + }{ + { + "Account method", + func() (interface{}, error) { + return k.Account(suite.ctx, nil) + }, + }, + { + "CosmosAccount method", + func() (interface{}, error) { + return k.CosmosAccount(suite.ctx, nil) + }, + }, + { + "ValidatorAccount method", + func() (interface{}, error) { + return k.ValidatorAccount(suite.ctx, nil) + }, + }, + { + "Balance method", + func() (interface{}, error) { + return k.Balance(suite.ctx, nil) + }, + }, + { + "Storage method", + func() (interface{}, error) { + return k.Storage(suite.ctx, nil) + }, + }, + { + "Code method", + func() (interface{}, error) { + return k.Code(suite.ctx, nil) + }, + }, + { + "EthCall method", + func() (interface{}, error) { + return k.EthCall(suite.ctx, nil) + }, + }, + { + "EstimateGas method", + func() (interface{}, error) { + return k.EstimateGas(suite.ctx, nil) + }, + }, + { + "TraceTx method", + func() (interface{}, error) { + return k.TraceTx(suite.ctx, nil) + }, + }, + { + "TraceBlock method", + func() (interface{}, error) { + return k.TraceBlock(suite.ctx, nil) + }, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + _, err := tc.queryFunc() + suite.Require().Error(err) + }) + } +} diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index a0087a93..95cb02b9 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -200,6 +200,7 @@ func (suite *KeeperTestSuite) SetupApp(checkTx bool) { valAddr := sdk.ValAddress(suite.address.Bytes()) validator, err := stakingtypes.NewValidator(valAddr, priv.PubKey(), stakingtypes.Description{}) + require.NoError(t, err) err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) require.NoError(t, err) err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) @@ -434,3 +435,88 @@ func (suite *KeeperTestSuite) TestBaseFee() { suite.enableFeemarket = false suite.enableLondonHF = true } + +func (suite *KeeperTestSuite) TestGetAccountStorage() { + testCases := []struct { + name string + malleate func() + expRes []int + }{ + { + "Only one account that's not a contract (no storage)", + func() {}, + []int{0}, + }, + { + "Two accounts - one contract (with storage), one wallet", + func() { + supply := big.NewInt(100) + suite.DeployTestContract(suite.T(), suite.address, supply) + }, + []int{2, 0}, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + tc.malleate() + i := 0 + suite.app.AccountKeeper.IterateAccounts(suite.ctx, func(account authtypes.AccountI) bool { + ethAccount, ok := account.(ethermint.EthAccountI) + if !ok { + // ignore non EthAccounts + return false + } + + addr := ethAccount.EthAddress() + storage := suite.app.EvmKeeper.GetAccountStorage(suite.ctx, addr) + + suite.Require().Equal(tc.expRes[i], len(storage)) + i++ + return false + }) + + }) + } + +} + +func (suite *KeeperTestSuite) TestGetAccountOrEmpty() { + empty := statedb.Account{ + Balance: new(big.Int), + CodeHash: types.EmptyCodeHash, + } + + supply := big.NewInt(100) + contractAddr := suite.DeployTestContract(suite.T(), suite.address, supply) + + testCases := []struct { + name string + addr common.Address + expEmpty bool + }{ + { + "unexisting account - get empty", + common.Address{}, + true, + }, + { + "existing contract account", + contractAddr, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + res := suite.app.EvmKeeper.GetAccountOrEmpty(suite.ctx, tc.addr) + if tc.expEmpty { + suite.Require().Equal(empty, res) + } else { + suite.Require().NotEqual(empty, res) + } + + }) + } +} diff --git a/x/evm/keeper/migrations_test.go b/x/evm/keeper/migrations_test.go new file mode 100644 index 00000000..3d680cda --- /dev/null +++ b/x/evm/keeper/migrations_test.go @@ -0,0 +1,31 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + evmkeeper "github.com/evmos/ethermint/x/evm/keeper" +) + +func (suite *KeeperTestSuite) TestMigrations() { + migrator := evmkeeper.NewMigrator(*suite.app.EvmKeeper) + + testCases := []struct { + name string + migrateFunc func(ctx sdk.Context) error + }{ + { + "Run Migrate1to2", + migrator.Migrate1to2, + }, + { + "Run Migrate2to3", + migrator.Migrate2to3, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.migrateFunc(suite.ctx) + suite.Require().NoError(err) + }) + } +} diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go new file mode 100644 index 00000000..1abf2a19 --- /dev/null +++ b/x/evm/keeper/msg_server_test.go @@ -0,0 +1,80 @@ +package keeper_test + +import ( + "math/big" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/evmos/ethermint/x/evm/statedb" + "github.com/evmos/ethermint/x/evm/types" +) + +func (suite *KeeperTestSuite) TestEthereumTx() { + var ( + err error + msg *types.MsgEthereumTx + signer ethtypes.Signer + vmdb *statedb.StateDB + chainCfg *params.ChainConfig + expectedGasUsed uint64 + ) + + testCases := []struct { + name string + malleate func() + expErr bool + }{ + { + "Deploy contract tx - insufficient gas", + func() { + msg, err = suite.createContractMsgTx( + vmdb.GetNonce(suite.address), + signer, + chainCfg, + big.NewInt(1), + ) + suite.Require().NoError(err) + }, + true, + }, + { + "Transfer funds tx", + func() { + msg, _, err = newEthMsgTx( + vmdb.GetNonce(suite.address), + suite.ctx.BlockHeight(), + suite.address, + chainCfg, + suite.signer, + signer, + ethtypes.AccessListTxType, + nil, + nil, + ) + suite.Require().NoError(err) + expectedGasUsed = params.TxGas + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + keeperParams := suite.app.EvmKeeper.GetParams(suite.ctx) + chainCfg = keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID()) + signer = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + vmdb = suite.StateDB() + + tc.malleate() + res, err := suite.app.EvmKeeper.EthereumTx(suite.ctx, msg) + if tc.expErr { + suite.Require().Error(err) + return + } + suite.Require().NoError(err) + suite.Require().Equal(expectedGasUsed, res.GasUsed) + suite.Require().False(res.Failed()) + }) + } +} diff --git a/x/evm/keeper/state_transition_benchmark_test.go b/x/evm/keeper/state_transition_benchmark_test.go index c9b3c092..795f6ff5 100644 --- a/x/evm/keeper/state_transition_benchmark_test.go +++ b/x/evm/keeper/state_transition_benchmark_test.go @@ -75,7 +75,7 @@ func newSignedEthTx( return ethTx, nil } -func newNativeMessage( +func newEthMsgTx( nonce uint64, blockHeight int64, address common.Address, @@ -85,14 +85,11 @@ func newNativeMessage( txType byte, data []byte, accessList ethtypes.AccessList, -) (core.Message, error) { - msgSigner := ethtypes.MakeSigner(cfg, big.NewInt(blockHeight)) - +) (*evmtypes.MsgEthereumTx, *big.Int, error) { var ( ethTx *ethtypes.Transaction baseFee *big.Int ) - switch txType { case ethtypes.LegacyTxType: templateLegacyTx.Nonce = nonce @@ -122,14 +119,32 @@ func newNativeMessage( ethTx = ethtypes.NewTx(templateDynamicFeeTx) baseFee = big.NewInt(3) default: - return nil, errors.New("unsupport tx type") + return nil, baseFee, errors.New("unsupport tx type") } msg := &evmtypes.MsgEthereumTx{} msg.FromEthereumTx(ethTx) msg.From = address.Hex() - if err := msg.Sign(ethSigner, krSigner); err != nil { + return msg, baseFee, msg.Sign(ethSigner, krSigner) +} + +func newNativeMessage( + nonce uint64, + blockHeight int64, + address common.Address, + cfg *params.ChainConfig, + krSigner keyring.Signer, + ethSigner ethtypes.Signer, + txType byte, + data []byte, + accessList ethtypes.AccessList, +) (core.Message, error) { + msgSigner := ethtypes.MakeSigner(cfg, big.NewInt(blockHeight)) + + msg, baseFee, err := newEthMsgTx(nonce, blockHeight, address, cfg, krSigner, ethSigner, txType, data, accessList) + + if err != nil { return nil, err } diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index 914db7d9..4955e5b3 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -9,10 +9,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/evmos/ethermint/tests" "github.com/evmos/ethermint/x/evm/keeper" + "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" "github.com/tendermint/tendermint/crypto/tmhash" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -346,40 +348,63 @@ func (suite *KeeperTestSuite) TestGasToRefund() { } func (suite *KeeperTestSuite) TestRefundGas() { + var ( + m core.Message + err error + ) + testCases := []struct { name string leftoverGas uint64 refundQuotient uint64 noError bool expGasRefund uint64 + malleate func() }{ { - "leftoverGas more than tx gas limit", - params.TxGas + 1, - params.RefundQuotient, - false, - params.TxGas + 1, + name: "leftoverGas more than tx gas limit", + leftoverGas: params.TxGas + 1, + refundQuotient: params.RefundQuotient, + noError: false, + expGasRefund: params.TxGas + 1, }, { - "leftoverGas equal to tx gas limit, insufficient fee collector account", - params.TxGas, - params.RefundQuotient, - true, - 0, + name: "leftoverGas equal to tx gas limit, insufficient fee collector account", + leftoverGas: params.TxGas, + refundQuotient: params.RefundQuotient, + noError: true, + expGasRefund: 0, }, { - "leftoverGas less than to tx gas limit", - params.TxGas - 1, - params.RefundQuotient, - true, - 0, + name: "leftoverGas less than to tx gas limit", + leftoverGas: params.TxGas - 1, + refundQuotient: params.RefundQuotient, + noError: true, + expGasRefund: 0, }, { - "no leftoverGas, refund half used gas ", - 0, - params.RefundQuotient, - true, - params.TxGas / params.RefundQuotient, + name: "no leftoverGas, refund half used gas ", + leftoverGas: 0, + refundQuotient: params.RefundQuotient, + noError: true, + expGasRefund: params.TxGas / params.RefundQuotient, + }, + { + name: "invalid Gas value in msg", + leftoverGas: 0, + refundQuotient: params.RefundQuotient, + noError: false, + expGasRefund: params.TxGas, + malleate: func() { + keeperParams := suite.app.EvmKeeper.GetParams(suite.ctx) + m, err = suite.createContractGethMsg( + suite.StateDB().GetNonce(suite.address), + ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()), + keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID()), + big.NewInt(-100), + ) + suite.Require().NoError(err) + }, }, } @@ -393,7 +418,7 @@ func (suite *KeeperTestSuite) TestRefundGas() { signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) vmdb := suite.StateDB() - m, err := newNativeMessage( + m, err = newNativeMessage( vmdb.GetNonce(suite.address), suite.ctx.BlockHeight(), suite.address, @@ -411,6 +436,11 @@ func (suite *KeeperTestSuite) TestRefundGas() { if tc.leftoverGas > m.Gas() { return } + + if tc.malleate != nil { + tc.malleate() + } + gasUsed := m.Gas() - tc.leftoverGas refund := keeper.GasToRefund(vmdb.GetRefund(), gasUsed, tc.refundQuotient) suite.Require().Equal(tc.expGasRefund, refund) @@ -486,7 +516,6 @@ func (suite *KeeperTestSuite) TestResetGasMeterAndConsumeGas() { } func (suite *KeeperTestSuite) TestEVMConfig() { - suite.SetupTest() cfg, err := suite.app.EvmKeeper.EVMConfig(suite.ctx) suite.Require().NoError(err) suite.Require().Equal(types.DefaultParams(), cfg.Params) @@ -497,8 +526,165 @@ func (suite *KeeperTestSuite) TestEVMConfig() { } func (suite *KeeperTestSuite) TestContractDeployment() { - suite.SetupTest() contractAddress := suite.DeployTestContract(suite.T(), suite.address, big.NewInt(10000000000000)) db := suite.StateDB() suite.Require().Greater(db.GetCodeSize(contractAddress), 0) } + +func (suite *KeeperTestSuite) TestApplyMessage() { + expectedGasUsed := params.TxGas + var msg core.Message + + config, err := suite.app.EvmKeeper.EVMConfig(suite.ctx) + suite.Require().NoError(err) + + keeperParams := suite.app.EvmKeeper.GetParams(suite.ctx) + chainCfg := keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID()) + signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + tracer := suite.app.EvmKeeper.Tracer(suite.ctx, msg, config.ChainConfig) + vmdb := suite.StateDB() + + msg, err = newNativeMessage( + vmdb.GetNonce(suite.address), + suite.ctx.BlockHeight(), + suite.address, + chainCfg, + suite.signer, + signer, + ethtypes.AccessListTxType, + nil, + nil, + ) + suite.Require().NoError(err) + + res, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, msg, tracer, true) + + suite.Require().NoError(err) + suite.Require().Equal(expectedGasUsed, res.GasUsed) + suite.Require().False(res.Failed()) +} + +func (suite *KeeperTestSuite) TestApplyMessageWithConfig() { + var ( + msg core.Message + err error + expectedGasUsed uint64 + config *types.EVMConfig + keeperParams types.Params + signer ethtypes.Signer + vmdb *statedb.StateDB + txConfig statedb.TxConfig + chainCfg *params.ChainConfig + ) + + testCases := []struct { + name string + malleate func() + expErr bool + }{ + { + "messsage applied ok", + func() { + msg, err = newNativeMessage( + vmdb.GetNonce(suite.address), + suite.ctx.BlockHeight(), + suite.address, + chainCfg, + suite.signer, + signer, + ethtypes.AccessListTxType, + nil, + nil, + ) + suite.Require().NoError(err) + }, + false, + }, + { + "call contract tx with config param EnableCall = false", + func() { + config.Params.EnableCall = false + msg, err = newNativeMessage( + vmdb.GetNonce(suite.address), + suite.ctx.BlockHeight(), + suite.address, + chainCfg, + suite.signer, + signer, + ethtypes.AccessListTxType, + nil, + nil, + ) + suite.Require().NoError(err) + }, + true, + }, + { + "create contract tx with config param EnableCreate = false", + func() { + msg, err = suite.createContractGethMsg(vmdb.GetNonce(suite.address), signer, chainCfg, big.NewInt(1)) + suite.Require().NoError(err) + config.Params.EnableCreate = false + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + expectedGasUsed = params.TxGas + + config, err = suite.app.EvmKeeper.EVMConfig(suite.ctx) + suite.Require().NoError(err) + + keeperParams = suite.app.EvmKeeper.GetParams(suite.ctx) + chainCfg = keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID()) + signer = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + vmdb = suite.StateDB() + txConfig = suite.app.EvmKeeper.TxConfig(suite.ctx, common.Hash{}) + + tc.malleate() + res, err := suite.app.EvmKeeper.ApplyMessageWithConfig(suite.ctx, msg, nil, true, config, txConfig) + + if tc.expErr { + suite.Require().Error(err) + return + } + + suite.Require().NoError(err) + suite.Require().False(res.Failed()) + suite.Require().Equal(expectedGasUsed, res.GasUsed) + }) + } + +} + +func (suite *KeeperTestSuite) createContractGethMsg(nonce uint64, signer ethtypes.Signer, cfg *params.ChainConfig, gasPrice *big.Int) (core.Message, error) { + + ethMsg, err := suite.createContractMsgTx(nonce, signer, cfg, gasPrice) + + if err != nil { + return nil, err + } + + msgSigner := ethtypes.MakeSigner(cfg, big.NewInt(suite.ctx.BlockHeight())) + return ethMsg.AsMessage(msgSigner, nil) +} + +func (suite *KeeperTestSuite) createContractMsgTx(nonce uint64, signer ethtypes.Signer, cfg *params.ChainConfig, gasPrice *big.Int) (*types.MsgEthereumTx, error) { + + contractCreateTx := ðtypes.AccessListTx{ + GasPrice: gasPrice, + Gas: params.TxGasContractCreation, + To: nil, + Data: []byte("contract_data"), + Nonce: nonce, + } + ethTx := ethtypes.NewTx(contractCreateTx) + ethMsg := &types.MsgEthereumTx{} + ethMsg.FromEthereumTx(ethTx) + ethMsg.From = suite.address.Hex() + + return ethMsg, ethMsg.Sign(signer, suite.signer) +} diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index e2c82a6c..1b89f482 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -6,6 +6,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -317,6 +318,40 @@ func (suite *KeeperTestSuite) TestSetCode() { } } +func (suite *KeeperTestSuite) TestKeeperSetCode() { + addr := tests.GenerateAddress() + baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()} + suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc) + + testCases := []struct { + name string + codeHash []byte + code []byte + }{ + { + "set code", + []byte("codeHash"), + []byte("this is the code"), + }, + { + "delete code", + []byte("codeHash"), + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.app.EvmKeeper.SetCode(suite.ctx, tc.codeHash, tc.code) + key := suite.app.GetKey(types.StoreKey) + store := prefix.NewStore(suite.ctx.KVStore(key), types.KeyPrefixCode) + code := store.Get(tc.codeHash) + + suite.Require().Equal(tc.code, code) + }) + } +} + func (suite *KeeperTestSuite) TestRefund() { testCases := []struct { name string @@ -384,8 +419,6 @@ func (suite *KeeperTestSuite) TestState() { } func (suite *KeeperTestSuite) TestCommittedState() { - suite.SetupTest() - key := common.BytesToHash([]byte("key")) value1 := common.BytesToHash([]byte("value1")) value2 := common.BytesToHash([]byte("value2")) @@ -487,8 +520,6 @@ func (suite *KeeperTestSuite) TestExist() { } func (suite *KeeperTestSuite) TestEmpty() { - suite.SetupTest() - testCases := []struct { name string address common.Address @@ -507,6 +538,7 @@ func (suite *KeeperTestSuite) TestEmpty() { for _, tc := range testCases { suite.Run(tc.name, func() { + suite.SetupTest() vmdb := suite.StateDB() tc.malleate(vmdb) @@ -811,3 +843,102 @@ func (suite *KeeperTestSuite) _TestForEachStorage() { storage = types.Storage{} } } + +func (suite *KeeperTestSuite) TestSetBalance() { + amount := big.NewInt(-10) + + testCases := []struct { + name string + addr common.Address + malleate func() + expErr bool + }{ + { + "address without funds - invalid amount", + suite.address, + func() {}, + true, + }, + { + "mint to address", + suite.address, + func() { + amount = big.NewInt(100) + }, + false, + }, + { + "burn from address", + suite.address, + func() { + amount = big.NewInt(60) + }, + false, + }, + { + "address with funds - invalid amount", + suite.address, + func() { + amount = big.NewInt(-10) + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + tc.malleate() + err := suite.app.EvmKeeper.SetBalance(suite.ctx, tc.addr, amount) + if tc.expErr { + suite.Require().Error(err) + } else { + balance := suite.app.EvmKeeper.GetBalance(suite.ctx, tc.addr) + suite.Require().NoError(err) + suite.Require().Equal(amount, balance) + } + + }) + } +} + +func (suite *KeeperTestSuite) TestDeleteAccount() { + supply := big.NewInt(100) + contractAddr := suite.DeployTestContract(suite.T(), suite.address, supply) + + testCases := []struct { + name string + addr common.Address + expErr bool + }{ + { + "remove address", + suite.address, + false, + }, + { + "remove unexistent address - returns nil error", + common.HexToAddress("unexistent_address"), + false, + }, + { + "remove deployed contract", + contractAddr, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + err := suite.app.EvmKeeper.DeleteAccount(suite.ctx, tc.addr) + if tc.expErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + balance := suite.app.EvmKeeper.GetBalance(suite.ctx, tc.addr) + suite.Require().Equal(new(big.Int), balance) + } + }) + } +} diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index 6f4074c7..f24cdeea 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -271,6 +271,8 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { accessList *ethtypes.AccessList expectPass bool enableFeemarket bool + from string + malleate func() }{ { name: "Enough balance", @@ -279,6 +281,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { cost: &oneInt, accessList: ðtypes.AccessList{}, expectPass: true, + from: suite.address.String(), }, { name: "Equal balance", @@ -287,6 +290,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { cost: &oneInt, accessList: ðtypes.AccessList{}, expectPass: true, + from: suite.address.String(), }, { name: "Higher gas limit, not enough balance", @@ -295,6 +299,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { cost: &oneInt, accessList: ðtypes.AccessList{}, expectPass: false, + from: suite.address.String(), }, { name: "Higher gas price, enough balance", @@ -303,6 +308,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { cost: &oneInt, accessList: ðtypes.AccessList{}, expectPass: true, + from: suite.address.String(), }, { name: "Higher gas price, not enough balance", @@ -311,6 +317,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { cost: &oneInt, accessList: ðtypes.AccessList{}, expectPass: false, + from: suite.address.String(), }, // This case is expected to be true because the fees can be deducted, but the tx // execution is going to fail because there is no more balance to pay the cost @@ -321,6 +328,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { cost: &fiftyInt, accessList: ðtypes.AccessList{}, expectPass: true, + from: suite.address.String(), }, // testcases with enableFeemarket enabled. { @@ -332,6 +340,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { accessList: ðtypes.AccessList{}, expectPass: false, enableFeemarket: true, + from: suite.address.String(), }, { name: "empty tip fee is valid to deduct", @@ -342,6 +351,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { accessList: ðtypes.AccessList{}, expectPass: true, enableFeemarket: true, + from: suite.address.String(), }, { name: "effectiveTip equal to gasTipCap", @@ -351,6 +361,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { accessList: ðtypes.AccessList{}, expectPass: true, enableFeemarket: true, + from: suite.address.String(), }, { name: "effectiveTip equal to (gasFeeCap - baseFee)", @@ -361,6 +372,42 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { accessList: ðtypes.AccessList{}, expectPass: true, enableFeemarket: true, + from: suite.address.String(), + }, + { + name: "Invalid from address", + gasLimit: 10, + gasPrice: &oneInt, + cost: &oneInt, + accessList: ðtypes.AccessList{}, + expectPass: false, + from: "", + }, + { + name: "Enough balance - with access list", + gasLimit: 10, + gasPrice: &oneInt, + cost: &oneInt, + accessList: ðtypes.AccessList{ + ethtypes.AccessTuple{ + Address: suite.address, + StorageKeys: []common.Hash{}, + }, + }, + expectPass: true, + from: suite.address.String(), + }, + { + name: "gasLimit < intrinsicGas during IsCheckTx", + gasLimit: 1, + gasPrice: &oneInt, + cost: &oneInt, + accessList: ðtypes.AccessList{}, + expectPass: false, + from: suite.address.String(), + malleate: func() { + suite.ctx = suite.ctx.WithIsCheckTx(true) + }, }, } @@ -370,6 +417,9 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { suite.SetupTest() vmdb := suite.StateDB() + if tc.malleate != nil { + tc.malleate() + } var amount, gasPrice, gasFeeCap, gasTipCap *big.Int if tc.cost != nil { amount = tc.cost.BigInt() @@ -399,7 +449,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { vmdb.Commit() tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &suite.address, amount, tc.gasLimit, gasPrice, gasFeeCap, gasTipCap, nil, tc.accessList) - tx.From = suite.address.String() + tx.From = tc.from txData, _ := evmtypes.UnpackTxData(tx.Data)