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/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" 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", uint64(suite.ctx.BlockHeight()), func() { header := tmproto.Header{} header.Height = suite.ctx.BlockHeight() suite.ctx = suite.ctx.WithBlockHeader(header) }, common.Hash{}, }, { "case 1.3: hash calculated from Tendermint header", uint64(suite.ctx.BlockHeight()), func() { suite.ctx = suite.ctx.WithBlockHeader(header) }, common.BytesToHash(hash), }, { "case 2.1: height lower than current one, hist info not found", 1, func() { suite.ctx = suite.ctx.WithBlockHeight(10) }, common.Hash{}, }, { "case 2.2: height lower than current one, invalid hist info header", 1, func() { suite.app.StakingKeeper.SetHistoricalInfo(suite.ctx, 1, &stakingtypes.HistoricalInfo{}) suite.ctx = suite.ctx.WithBlockHeight(10) }, common.Hash{}, }, { "case 2.3: height lower than current one, calculated from hist info header", 1, func() { histInfo := &stakingtypes.HistoricalInfo{ Header: header, } suite.app.StakingKeeper.SetHistoricalInfo(suite.ctx, 1, histInfo) suite.ctx = suite.ctx.WithBlockHeight(10) }, common.BytesToHash(hash), }, { "case 3: height greater than current one", 200, func() {}, common.Hash{}, }, } 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() testCases := []struct { msg string malleate func() expPass bool }{ { "validator not found", func() { header := suite.ctx.BlockHeader() header.ProposerAddress = []byte{} suite.ctx = suite.ctx.WithBlockHeader(header) }, false, }, { "success", func() { 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) suite.Require().NoError(err) header := suite.ctx.BlockHeader() header.ProposerAddress = valConsAddr.Bytes() suite.ctx = suite.ctx.WithBlockHeader(header) _, found := suite.app.StakingKeeper.GetValidatorByConsAddr(suite.ctx, valConsAddr.Bytes()) suite.Require().True(found) suite.Require().NotEmpty(suite.ctx.BlockHeader().ProposerAddress) }, true, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { suite.SetupTest() // reset tc.malleate() coinbase, err := suite.app.EvmKeeper.GetCoinbaseAddress(suite.ctx) if tc.expPass { suite.Require().NoError(err) suite.Require().Equal(valOpAddr, coinbase) } else { suite.Require().Error(err) } }) } } func (suite *KeeperTestSuite) TestGetEthIntrinsicGas() { testCases := []struct { 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, nil, 1, false, true, params.TxGas, }, { "with one zero data, no accesslist, not contract creation, not homestead, not istanbul", []byte{0}, nil, 1, false, true, params.TxGas + params.TxDataZeroGas*1, }, { "with one non zero data, no accesslist, not contract creation, not homestead, not istanbul", []byte{1}, nil, 1, true, true, params.TxGas + params.TxDataNonZeroGasFrontier*1, }, // we are not able to test the ErrGasUintOverflow due to RAM limitation // { // "with big data size overflow", // make([]byte, 271300000000000000), // nil, // 1, // false, // false, // 0, // }, { "no data, one accesslist, not contract creation, not homestead, not istanbul", nil, []ethtypes.AccessTuple{ {}, }, 1, false, true, params.TxGas + params.TxAccessListAddressGas, }, { "no data, one accesslist with one storageKey, not contract creation, not homestead, not istanbul", nil, []ethtypes.AccessTuple{ {StorageKeys: make([]common.Hash, 1)}, }, 1, false, true, params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas*1, }, { "no data, no accesslist, is contract creation, is homestead, not istanbul", nil, nil, 2, true, true, params.TxGasContractCreation, }, { "with one zero data, no accesslist, not contract creation, is homestead, is istanbul", []byte{1}, nil, 3, false, true, params.TxGas + params.TxDataNonZeroGasEIP2028*1, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.SetupTest() // reset 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, ) suite.Require().NoError(err) gas, err := suite.app.EvmKeeper.GetEthIntrinsicGas(suite.ctx, m, ethCfg, tc.isContractCreation) if tc.noError { suite.Require().NoError(err) } else { suite.Require().Error(err) } suite.Require().Equal(tc.expGas, gas) }) } } func (suite *KeeperTestSuite) TestGasToRefund() { testCases := []struct { name string gasconsumed uint64 refundQuotient uint64 expGasRefund uint64 expPanic bool }{ { "gas refund 5", 5, 1, 5, false, }, { "gas refund 10", 10, 1, 10, false, }, { "gas refund availableRefund", 11, 1, 10, false, }, { "gas refund quotient 0", 11, 0, 0, true, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.mintFeeCollector = true suite.SetupTest() // reset vmdb := suite.StateDB() vmdb.AddRefund(10) if tc.expPanic { panicF := func() { keeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient) } suite.Require().Panics(panicF) } else { gr := keeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient) suite.Require().Equal(tc.expGasRefund, gr) } }) } suite.mintFeeCollector = false } func (suite *KeeperTestSuite) TestRefundGas() { testCases := []struct { name string leftoverGas uint64 refundQuotient uint64 noError bool expGasRefund uint64 }{ { "leftoverGas more than tx gas limit", params.TxGas + 1, params.RefundQuotient, false, params.TxGas + 1, }, { "leftoverGas equal to tx gas limit, insufficient fee collector account", params.TxGas, params.RefundQuotient, true, 0, }, { "leftoverGas less than to tx gas limit", params.TxGas - 1, params.RefundQuotient, true, 0, }, { "no leftoverGas, refund half used gas ", 0, params.RefundQuotient, true, params.TxGas / params.RefundQuotient, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.mintFeeCollector = true suite.SetupTest() // reset keeperParams := suite.app.EvmKeeper.GetParams(suite.ctx) ethCfg := keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID()) signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) vmdb := suite.StateDB() m, err := newNativeMessage( vmdb.GetNonce(suite.address), suite.ctx.BlockHeight(), suite.address, ethCfg, suite.signer, signer, ethtypes.AccessListTxType, nil, nil, ) suite.Require().NoError(err) vmdb.AddRefund(params.TxGas) if tc.leftoverGas > m.Gas() { return } 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") if tc.noError { suite.Require().NoError(err) } else { suite.Require().Error(err) } }) } suite.mintFeeCollector = false } func (suite *KeeperTestSuite) TestResetGasMeterAndConsumeGas() { testCases := []struct { name string gasConsumed uint64 gasUsed uint64 expPanic bool }{ { "gas consumed 5, used 5", 5, 5, false, }, { "gas consumed 5, used 10", 5, 10, false, }, { "gas consumed 10, used 10", 10, 10, false, }, { "gas consumed 11, used 10, NegativeGasConsumed panic", 11, 10, true, }, { "gas consumed 1, used 10, overflow panic", 1, math.MaxUint64, true, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.SetupTest() // reset panicF := func() { gm := sdk.NewGasMeter(10) gm.ConsumeGas(tc.gasConsumed, "") ctx := suite.ctx.WithGasMeter(gm) suite.app.EvmKeeper.ResetGasMeterAndConsumeGas(ctx, tc.gasUsed) } if tc.expPanic { suite.Require().Panics(panicF) } else { suite.Require().NotPanics(panicF) } }) } } 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) // 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() { suite.SetupTest() contractAddress := suite.DeployTestContract(suite.T(), suite.address, big.NewInt(10000000000000)) db := suite.StateDB() suite.Require().Greater(db.GetCodeSize(contractAddress), 0) }