laconicd-deprecated/x/evm/keeper/keeper_test.go

525 lines
16 KiB
Go
Raw Normal View History

package keeper_test
import (
_ "embed"
"encoding/json"
"math"
"math/big"
"testing"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
sdkmath "cosmossdk.io/math"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
2021-04-17 10:00:07 +00:00
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/evmos/ethermint/app"
"github.com/evmos/ethermint/crypto/ethsecp256k1"
"github.com/evmos/ethermint/encoding"
"github.com/evmos/ethermint/server/config"
"github.com/evmos/ethermint/tests"
ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/statedb"
"github.com/evmos/ethermint/x/evm/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
abci "github.com/tendermint/tendermint/abci/types"
2021-08-31 15:58:46 +00:00
"github.com/tendermint/tendermint/crypto/tmhash"
2021-04-17 10:00:07 +00:00
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
2021-08-31 15:58:46 +00:00
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
"github.com/tendermint/tendermint/version"
)
var testTokens = sdkmath.NewIntWithDecimal(1000, 18)
type KeeperTestSuite struct {
suite.Suite
ctx sdk.Context
app *app.EthermintApp
queryClient types.QueryClient
2021-09-03 18:06:36 +00:00
address common.Address
consAddress sdk.ConsAddress
// for generate test tx
clientCtx client.Context
ethSigner ethtypes.Signer
appCodec codec.Codec
signer keyring.Signer
enableFeemarket bool
enableLondonHF bool
mintFeeCollector bool
denom string
}
var s *KeeperTestSuite
func TestKeeperTestSuite(t *testing.T) {
s = new(KeeperTestSuite)
s.enableFeemarket = false
s.enableLondonHF = true
suite.Run(t, s)
// Run Ginkgo integration tests
RegisterFailHandler(Fail)
RunSpecs(t, "Keeper Suite")
}
func (suite *KeeperTestSuite) SetupTest() {
checkTx := false
suite.app = app.Setup(checkTx, nil)
suite.SetupApp(checkTx)
}
// SetupApp setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
func (suite *KeeperTestSuite) SetupApp(checkTx bool) {
t := suite.T()
// account key, use a constant account to keep unit test deterministic.
ecdsaPriv, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
require.NoError(t, err)
priv := &ethsecp256k1.PrivKey{
Key: crypto.FromECDSA(ecdsaPriv),
}
2021-09-03 18:06:36 +00:00
suite.address = common.BytesToAddress(priv.PubKey().Address().Bytes())
suite.signer = tests.NewSigner(priv)
// consensus key
priv, err = ethsecp256k1.GenerateKey()
require.NoError(t, err)
suite.consAddress = sdk.ConsAddress(priv.PubKey().Address())
suite.app = app.Setup(checkTx, func(app *app.EthermintApp, genesis simapp.GenesisState) simapp.GenesisState {
feemarketGenesis := feemarkettypes.DefaultGenesisState()
if suite.enableFeemarket {
feemarketGenesis.Params.EnableHeight = 1
feemarketGenesis.Params.NoBaseFee = false
} else {
feemarketGenesis.Params.NoBaseFee = true
}
genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis)
if !suite.enableLondonHF {
evmGenesis := types.DefaultGenesisState()
maxInt := sdkmath.NewInt(math.MaxInt64)
evmGenesis.Params.ChainConfig.LondonBlock = &maxInt
evmGenesis.Params.ChainConfig.ArrowGlacierBlock = &maxInt
evmGenesis.Params.ChainConfig.GrayGlacierBlock = &maxInt
evmGenesis.Params.ChainConfig.MergeNetsplitBlock = &maxInt
genesis[types.ModuleName] = app.AppCodec().MustMarshalJSON(evmGenesis)
}
return genesis
})
if suite.mintFeeCollector {
// mint some coin to fee collector
coins := sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdkmath.NewInt(int64(params.TxGas)-1)))
genesisState := app.NewTestGenesisState(suite.app.AppCodec())
balances := []banktypes.Balance{
{
Address: suite.app.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName).String(),
Coins: coins,
},
}
var bankGenesis banktypes.GenesisState
suite.app.AppCodec().MustUnmarshalJSON(genesisState[banktypes.ModuleName], &bankGenesis)
// Update balances and total supply
bankGenesis.Balances = append(bankGenesis.Balances, balances...)
bankGenesis.Supply = bankGenesis.Supply.Add(coins...)
genesisState[banktypes.ModuleName] = suite.app.AppCodec().MustMarshalJSON(&bankGenesis)
// we marshal the genesisState of all module to a byte array
stateBytes, err := tmjson.MarshalIndent(genesisState, "", " ")
require.NoError(t, err)
rpc: `eth_feeHistory` (#734) * Problem: missing json rpc of eth_feeHistory #685 add oracle backend space ready structure ok refactoring return feehistory data flow ok basefee set gas used ratio computing reward add testing add gas used prepare data fill reward increase coin fixing api add mac add launch gas used ratio ok print element reward workes reward working fix panic value correct remove debugging log tidy up tidy up remove oracle tidy up fix handler crash add unit test tidy up add limit check reformat fix lint fix lint fix lint fix lint Update rpc/ethereum/backend/feebackend.go thanks Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Update rpc/ethereum/backend/feebackend.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Update rpc/ethereum/backend/feebackend.go thanks Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Update rpc/ethereum/backend/feebackend.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> fix compile error split lines remove temporary string conversion return error if gaslimit is 0 move OneFeeHistory to types add comment only err check Update rpc/ethereum/backend/feebackend.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Update rpc/ethereum/backend/feebackend.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> tidy up add feehistory-cap * Apply suggestions from code review * changelog Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Federico Kunze Küllmer <federico.kunze94@gmail.com>
2021-11-17 11:58:52 +00:00
// Initialize the chain
suite.app.InitChain(
abci.RequestInitChain{
ChainId: "ethermint_9000-1",
Validators: []abci.ValidatorUpdate{},
ConsensusParams: app.DefaultConsensusParams,
AppStateBytes: stateBytes,
},
)
}
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{
Height: 1,
ChainID: "ethermint_9000-1",
Time: time.Now().UTC(),
ProposerAddress: suite.consAddress.Bytes(),
2021-08-31 15:58:46 +00:00
Version: tmversion.Consensus{
Block: version.BlockProtocol,
},
LastBlockId: tmproto.BlockID{
Hash: tmhash.Sum([]byte("block_id")),
PartSetHeader: tmproto.PartSetHeader{
Total: 11,
Hash: tmhash.Sum([]byte("partset_header")),
},
},
AppHash: tmhash.Sum([]byte("app")),
DataHash: tmhash.Sum([]byte("data")),
EvidenceHash: tmhash.Sum([]byte("evidence")),
ValidatorsHash: tmhash.Sum([]byte("validators")),
NextValidatorsHash: tmhash.Sum([]byte("next_validators")),
ConsensusHash: tmhash.Sum([]byte("consensus")),
LastResultsHash: tmhash.Sum([]byte("last_result")),
})
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.EvmKeeper)
suite.queryClient = types.NewQueryClient(queryHelper)
acc := &ethermint.EthAccount{
2021-04-17 10:00:07 +00:00
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
2021-09-03 18:06:36 +00:00
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).String(),
}
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
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)
require.NoError(t, err)
suite.app.StakingKeeper.SetValidator(suite.ctx, validator)
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
suite.appCodec = encodingConfig.Codec
suite.denom = evmtypes.DefaultEVMDenom
}
func (suite *KeeperTestSuite) EvmDenom() string {
ctx := sdk.WrapSDKContext(suite.ctx)
rsp, _ := suite.queryClient.Params(ctx, &types.QueryParamsRequest{})
return rsp.Params.EvmDenom
}
// Commit and begin new block
func (suite *KeeperTestSuite) Commit() {
2021-08-31 15:58:46 +00:00
_ = suite.app.Commit()
header := suite.ctx.BlockHeader()
header.Height += 1
suite.app.BeginBlock(abci.RequestBeginBlock{
Header: header,
})
// update ctx
suite.ctx = suite.app.BaseApp.NewContext(false, header)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.EvmKeeper)
suite.queryClient = types.NewQueryClient(queryHelper)
}
func (suite *KeeperTestSuite) StateDB() *statedb.StateDB {
return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes())))
}
// DeployTestContract deploy a test erc20 contract and returns the contract address
func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner common.Address, supply *big.Int) common.Address {
ctx := sdk.WrapSDKContext(suite.ctx)
chainID := suite.app.EvmKeeper.ChainID()
ctorArgs, err := types.ERC20Contract.ABI.Pack("", owner, supply)
require.NoError(t, err)
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
data := append(types.ERC20Contract.Bin, ctorArgs...)
args, err := json.Marshal(&types.TransactionArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
})
require.NoError(t, err)
res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{
Args: args,
GasCap: uint64(config.DefaultGasCap),
ProposerAddress: suite.ctx.BlockHeader().ProposerAddress,
})
require.NoError(t, err)
var erc20DeployTx *types.MsgEthereumTx
if suite.enableFeemarket {
erc20DeployTx = types.NewTxContract(
chainID,
nonce,
nil, // amount
res.Gas, // gasLimit
nil, // gasPrice
suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx),
big.NewInt(1),
data, // input
&ethtypes.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)
rsp, err := suite.app.EvmKeeper.EthereumTx(ctx, erc20DeployTx)
require.NoError(t, err)
require.Empty(t, rsp.VmError)
return crypto.CreateAddress(suite.address, nonce)
}
func (suite *KeeperTestSuite) TransferERC20Token(t require.TestingT, contractAddr, from, to common.Address, amount *big.Int) *types.MsgEthereumTx {
ctx := sdk.WrapSDKContext(suite.ctx)
chainID := suite.app.EvmKeeper.ChainID()
transferData, err := types.ERC20Contract.ABI.Pack("transfer", to, amount)
require.NoError(t, err)
args, err := json.Marshal(&types.TransactionArgs{To: &contractAddr, From: &from, Data: (*hexutil.Bytes)(&transferData)})
require.NoError(t, err)
res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{
Args: args,
GasCap: 25_000_000,
ProposerAddress: suite.ctx.BlockHeader().ProposerAddress,
})
require.NoError(t, err)
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
var ercTransferTx *types.MsgEthereumTx
if suite.enableFeemarket {
ercTransferTx = types.NewTx(
chainID,
nonce,
&contractAddr,
nil,
res.Gas,
nil,
suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx),
big.NewInt(1),
transferData,
&ethtypes.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)
rsp, err := suite.app.EvmKeeper.EthereumTx(ctx, ercTransferTx)
require.NoError(t, err)
require.Empty(t, rsp.VmError)
return ercTransferTx
}
// DeployTestMessageCall deploy a test erc20 contract and returns the contract address
func (suite *KeeperTestSuite) DeployTestMessageCall(t require.TestingT) common.Address {
ctx := sdk.WrapSDKContext(suite.ctx)
chainID := suite.app.EvmKeeper.ChainID()
data := types.TestMessageCall.Bin
args, err := json.Marshal(&types.TransactionArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
})
require.NoError(t, err)
res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{
Args: args,
GasCap: uint64(config.DefaultGasCap),
ProposerAddress: suite.ctx.BlockHeader().ProposerAddress,
})
require.NoError(t, err)
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
var erc20DeployTx *types.MsgEthereumTx
if suite.enableFeemarket {
erc20DeployTx = types.NewTxContract(
chainID,
nonce,
nil, // amount
res.Gas, // gasLimit
nil, // gasPrice
suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx),
big.NewInt(1),
data, // input
&ethtypes.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)
rsp, err := suite.app.EvmKeeper.EthereumTx(ctx, erc20DeployTx)
require.NoError(t, err)
require.Empty(t, rsp.VmError)
return crypto.CreateAddress(suite.address, nonce)
}
func (suite *KeeperTestSuite) TestBaseFee() {
testCases := []struct {
name string
enableLondonHF bool
enableFeemarket bool
expectBaseFee *big.Int
}{
{"not enable london HF, not enable feemarket", false, false, nil},
{"enable london HF, not enable feemarket", true, false, big.NewInt(0)},
{"enable london HF, enable feemarket", true, true, big.NewInt(1000000000)},
{"not enable london HF, enable feemarket", false, true, nil},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.enableFeemarket = tc.enableFeemarket
suite.enableLondonHF = tc.enableLondonHF
suite.SetupTest()
suite.app.EvmKeeper.BeginBlock(suite.ctx, abci.RequestBeginBlock{})
params := suite.app.EvmKeeper.GetParams(suite.ctx)
ethCfg := params.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg)
suite.Require().Equal(tc.expectBaseFee, baseFee)
})
}
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)
}
})
}
}