package keeper_test
import (
"fmt"
"math"
"math/big"
"github.com/cerc-io/laconicd/tests"
"github.com/cerc-io/laconicd/x/evm/keeper"
"github.com/cerc-io/laconicd/x/evm/statedb"
"github.com/cerc-io/laconicd/x/evm/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
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/tendermint/tendermint/crypto/tmhash"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtypes "github.com/tendermint/tendermint/types"
)
func (suite *KeeperTestSuite) TestGetHashFn() {
header := suite.ctx.BlockHeader()
h, _ := tmtypes.HeaderFromProto(&header)
hash := h.Hash()
testCases := []struct {
msg string
height uint64
malleate func()
expHash common.Hash
}{
{
"case 1.1: context hash cached",
uint64(suite.ctx.BlockHeight()),
func() {
suite.ctx = suite.ctx.WithHeaderHash(tmhash.Sum([]byte("header")))
},
common.BytesToHash(tmhash.Sum([]byte("header"))),
"case 1.2: failed to cast Tendermint header",
header := tmproto.Header{}
header.Height = suite.ctx.BlockHeight()
suite.ctx = suite.ctx.WithBlockHeader(header)
common.Hash{},
"case 1.3: hash calculated from Tendermint header",
common.BytesToHash(hash),
"case 2.1: height lower than current one, hist info not found",
1,
suite.ctx = suite.ctx.WithBlockHeight(10)
"case 2.2: height lower than current one, invalid hist info header",
suite.app.StakingKeeper.SetHistoricalInfo(suite.ctx, 1, &stakingtypes.HistoricalInfo{})
"case 2.3: height lower than current one, calculated from hist info header",
histInfo := &stakingtypes.HistoricalInfo{
Header: header,
}
suite.app.StakingKeeper.SetHistoricalInfo(suite.ctx, 1, histInfo)
"case 3: height greater than current one",
200,
func() {},
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
hash := suite.app.EvmKeeper.GetHashFn(suite.ctx)(tc.height)
suite.Require().Equal(tc.expHash, hash)
})
func (suite *KeeperTestSuite) TestGetCoinbaseAddress() {
valOpAddr := tests.GenerateAddress()
expPass bool
"validator not found",
header.ProposerAddress = []byte{}
false,
"success",
valConsAddr, privkey := tests.NewAddrKey()
pkAny, err := codectypes.NewAnyWithValue(privkey.PubKey())
suite.Require().NoError(err)
validator := stakingtypes.Validator{
OperatorAddress: sdk.ValAddress(valOpAddr.Bytes()).String(),
ConsensusPubkey: pkAny,
suite.app.StakingKeeper.SetValidator(suite.ctx, validator)
err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator)
header.ProposerAddress = valConsAddr.Bytes()
_, found := suite.app.StakingKeeper.GetValidatorByConsAddr(suite.ctx, valConsAddr.Bytes())
suite.Require().True(found)
suite.Require().NotEmpty(suite.ctx.BlockHeader().ProposerAddress)
true,
proposerAddress := suite.ctx.BlockHeader().ProposerAddress
coinbase, err := suite.app.EvmKeeper.GetCoinbaseAddress(suite.ctx, sdk.ConsAddress(proposerAddress))
if tc.expPass {
suite.Require().Equal(valOpAddr, coinbase)
} else {
suite.Require().Error(err)
func (suite *KeeperTestSuite) TestGetEthIntrinsicGas() {
name string
data []byte
accessList ethtypes.AccessList
height int64
isContractCreation bool
noError bool
expGas uint64
"no data, no accesslist, not contract creation, not homestead, not istanbul",
nil,
params.TxGas,
"with one zero data, no accesslist, not contract creation, not homestead, not istanbul",
[]byte{0},
params.TxGas + params.TxDataZeroGas*1,
"with one non zero data, no accesslist, not contract creation, not homestead, not istanbul",
[]byte{1},
params.TxGas + params.TxDataNonZeroGasFrontier*1,
"no data, one accesslist, not contract creation, not homestead, not istanbul",
[]ethtypes.AccessTuple{
{},
params.TxGas + params.TxAccessListAddressGas,
"no data, one accesslist with one storageKey, not contract creation, not homestead, not istanbul",
{StorageKeys: make([]common.Hash, 1)},
params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas*1,
"no data, no accesslist, is contract creation, is homestead, not istanbul",
2,
params.TxGasContractCreation,
"with one zero data, no accesslist, not contract creation, is homestead, is istanbul",
3,
params.TxGas + params.TxDataNonZeroGasEIP2028*1,
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
params := suite.app.EvmKeeper.GetParams(suite.ctx)
ethCfg := params.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
ethCfg.HomesteadBlock = big.NewInt(2)
ethCfg.IstanbulBlock = big.NewInt(3)
signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
suite.ctx = suite.ctx.WithBlockHeight(tc.height)
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
m, err := newNativeMessage(
nonce,
suite.ctx.BlockHeight(),
suite.address,
ethCfg,
suite.signer,
signer,
ethtypes.AccessListTxType,
tc.data,
tc.accessList,
gas, err := suite.app.EvmKeeper.GetEthIntrinsicGas(suite.ctx, m, ethCfg, tc.isContractCreation)
if tc.noError {
suite.Require().Equal(tc.expGas, gas)
func (suite *KeeperTestSuite) TestGasToRefund() {
gasconsumed uint64
refundQuotient uint64
expGasRefund uint64
expPanic bool
"gas refund 5",
5,
"gas refund 10",
10,
"gas refund availableRefund",
11,
"gas refund quotient 0",
0,
suite.mintFeeCollector = true
vmdb := suite.StateDB()
vmdb.AddRefund(10)
if tc.expPanic {
panicF := func() {
keeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient)
suite.Require().Panics(panicF)
gr := keeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient)
suite.Require().Equal(tc.expGasRefund, gr)
suite.mintFeeCollector = false
func (suite *KeeperTestSuite) TestRefundGas() {
var (
m core.Message
err error
leftoverGas uint64
name: "leftoverGas more than tx gas limit",
leftoverGas: params.TxGas + 1,
refundQuotient: params.RefundQuotient,
noError: false,
expGasRefund: params.TxGas + 1,
name: "leftoverGas equal to tx gas limit, insufficient fee collector account",
leftoverGas: params.TxGas,
noError: true,
expGasRefund: 0,
name: "leftoverGas less than to tx gas limit",
leftoverGas: params.TxGas - 1,
name: "no leftoverGas, refund half used gas ",
leftoverGas: 0,
expGasRefund: params.TxGas / params.RefundQuotient,
name: "invalid Gas value in msg",
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),
ethCfg := keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
m, err = newNativeMessage(
vmdb.GetNonce(suite.address),
vmdb.AddRefund(params.TxGas)
if tc.leftoverGas > m.Gas() {
return
if tc.malleate != nil {
gasUsed := m.Gas() - tc.leftoverGas
refund := keeper.GasToRefund(vmdb.GetRefund(), gasUsed, tc.refundQuotient)
suite.Require().Equal(tc.expGasRefund, refund)
err = suite.app.EvmKeeper.RefundGas(suite.ctx, m, refund, "aphoton")
func (suite *KeeperTestSuite) TestResetGasMeterAndConsumeGas() {
gasConsumed uint64
gasUsed uint64
"gas consumed 5, used 5",
"gas consumed 5, used 10",
"gas consumed 10, used 10",
"gas consumed 11, used 10, NegativeGasConsumed panic",
"gas consumed 1, used 10, overflow panic",
math.MaxUint64,
gm := sdk.NewGasMeter(10)
gm.ConsumeGas(tc.gasConsumed, "")
ctx := suite.ctx.WithGasMeter(gm)
suite.app.EvmKeeper.ResetGasMeterAndConsumeGas(ctx, tc.gasUsed)
suite.Require().NotPanics(panicF)
func (suite *KeeperTestSuite) TestEVMConfig() {
cfg, err := suite.app.EvmKeeper.EVMConfig(suite.ctx, proposerAddress, big.NewInt(9000))
suite.Require().Equal(types.DefaultParams(), cfg.Params)
// london hardfork is enabled by default
suite.Require().Equal(big.NewInt(0), cfg.BaseFee)
suite.Require().Equal(suite.address, cfg.CoinBase)
suite.Require().Equal(types.DefaultParams().ChainConfig.EthereumConfig(big.NewInt(9000)), cfg.ChainConfig)
func (suite *KeeperTestSuite) TestContractDeployment() {
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, proposerAddress, big.NewInt(9000))
chainCfg := keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
tracer := suite.app.EvmKeeper.Tracer(suite.ctx, msg, config.ChainConfig)
msg, err = newNativeMessage(
chainCfg,
res, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, msg, tracer, true)
suite.Require().Equal(expectedGasUsed, res.GasUsed)
suite.Require().False(res.Failed())
func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
msg core.Message
expectedGasUsed uint64
config *types.EVMConfig
keeperParams types.Params
signer ethtypes.Signer
vmdb *statedb.StateDB
txConfig statedb.TxConfig
chainCfg *params.ChainConfig
expErr bool
"messsage applied ok",
"call contract tx with config param EnableCall = false",
config.Params.EnableCall = false
"create contract tx with config param EnableCreate = false",
msg, err = suite.createContractGethMsg(vmdb.GetNonce(suite.address), signer, chainCfg, big.NewInt(1))
config.Params.EnableCreate = false
suite.SetupTest()
expectedGasUsed = params.TxGas
config, err = suite.app.EvmKeeper.EVMConfig(suite.ctx, proposerAddress, big.NewInt(9000))
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{})
res, err := suite.app.EvmKeeper.ApplyMessageWithConfig(suite.ctx, msg, nil, true, config, txConfig)
if tc.expErr {
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)
func (suite *KeeperTestSuite) TestGetProposerAddress() {
var a sdk.ConsAddress
address := sdk.ConsAddress(suite.address.Bytes())
proposerAddress := sdk.ConsAddress(suite.ctx.BlockHeader().ProposerAddress)
adr sdk.ConsAddress
expAdr sdk.ConsAddress
"proposer address provided",
address,
"nil proposer address provided",
proposerAddress,
"typed nil proposer address provided",
a,
suite.Require().Equal(tc.expAdr, keeper.GetProposerAddress(suite.ctx, tc.adr))