ime(evm): improve code coverage for EVM keeper (#1396)

* [issue-1018] add new tests on evm module keeper

* [issue-1018] add more new tests on evm module keeper

* [issue-1018] add more new tests on evm module keeper (state_transition, statedb, utils)

* [issue-1018] add more new tests on evm module keeper (msg_server)

* [issue-1018] fix code style

* [issue-1018] add changes in CHANGELOG

* [issue-1018] add missing error check in grpc_query_test

* [issue-1018] fix failing tests

* [issue-1018] add changes based on review comments (grpc_query_test & abci_test)

* [issue-1018] add GenerateAddress in TestEthCall (grpc_query_test)

* [issue-1018] remove unnecessary SetupTest calls

* [issue-1018] refactor SetupTest calls (grpc_query_test & statedb_test)
This commit is contained in:
Tomas Guerra 2022-10-21 13:59:03 -03:00 committed by GitHub
parent f4fae00987
commit f04b289e75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1110 additions and 107 deletions

View File

@ -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

18
x/evm/keeper/abci_test.go Normal file
View File

@ -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)
}

View File

@ -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)
})
}
}

View File

@ -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)
}
})
}
}

View File

@ -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)
})
}
}

View File

@ -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())
})
}
}

View File

@ -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
}

View File

@ -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 := &ethtypes.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)
}

View File

@ -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)
}
})
}
}

View File

@ -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: &ethtypes.AccessList{},
expectPass: true,
from: suite.address.String(),
},
{
name: "Equal balance",
@ -287,6 +290,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
cost: &oneInt,
accessList: &ethtypes.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: &ethtypes.AccessList{},
expectPass: false,
from: suite.address.String(),
},
{
name: "Higher gas price, enough balance",
@ -303,6 +308,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
cost: &oneInt,
accessList: &ethtypes.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: &ethtypes.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: &ethtypes.AccessList{},
expectPass: true,
from: suite.address.String(),
},
// testcases with enableFeemarket enabled.
{
@ -332,6 +340,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
accessList: &ethtypes.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: &ethtypes.AccessList{},
expectPass: true,
enableFeemarket: true,
from: suite.address.String(),
},
{
name: "effectiveTip equal to gasTipCap",
@ -351,6 +361,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
accessList: &ethtypes.AccessList{},
expectPass: true,
enableFeemarket: true,
from: suite.address.String(),
},
{
name: "effectiveTip equal to (gasFeeCap - baseFee)",
@ -361,6 +372,42 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
accessList: &ethtypes.AccessList{},
expectPass: true,
enableFeemarket: true,
from: suite.address.String(),
},
{
name: "Invalid from address",
gasLimit: 10,
gasPrice: &oneInt,
cost: &oneInt,
accessList: &ethtypes.AccessList{},
expectPass: false,
from: "",
},
{
name: "Enough balance - with access list",
gasLimit: 10,
gasPrice: &oneInt,
cost: &oneInt,
accessList: &ethtypes.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: &ethtypes.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)