laconicd/x/evm/keeper/keeper_test.go
yihuang 5e15c64d02
fix: make gas estimation test reproducible (#890)
Closes: #536

Solution: Use a fixed test account.

The source of non-deterministic is tx's intrinsic gas is different according to different number of zeros in input data,
so to make sure the gas number is reproducible, and the randomly generated test address is included in that.
2022-01-07 14:04:16 +01:00

418 lines
13 KiB
Go

package keeper_test
import (
_ "embed"
"encoding/json"
"math"
"math/big"
"testing"
"time"
"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"
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"
tmjson "github.com/tendermint/tendermint/libs/json"
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
"github.com/tharsis/ethermint/app"
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
"github.com/tharsis/ethermint/encoding"
"github.com/tharsis/ethermint/server/config"
"github.com/tharsis/ethermint/tests"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/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"
"github.com/tendermint/tendermint/crypto/tmhash"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
"github.com/tendermint/tendermint/version"
)
var testTokens = sdk.NewIntWithDecimal(1000, 18)
type KeeperTestSuite struct {
suite.Suite
ctx sdk.Context
app *app.EthermintApp
queryClient types.QueryClient
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
}
/// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) {
checkTx := false
// 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),
}
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 {
if suite.enableFeemarket {
feemarketGenesis := feemarkettypes.DefaultGenesisState()
feemarketGenesis.Params.EnableHeight = 1
feemarketGenesis.Params.NoBaseFee = false
feemarketGenesis.BaseFee = sdk.NewInt(feemarketGenesis.Params.InitialBaseFee)
genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis)
}
if !suite.enableLondonHF {
evmGenesis := types.DefaultGenesisState()
maxInt := sdk.NewInt(math.MaxInt64)
evmGenesis.Params.ChainConfig.LondonBlock = &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, sdk.NewInt(int64(params.TxGas)-1)))
genesisState := app.ModuleBasics.DefaultGenesis(suite.app.AppCodec())
balances := []banktypes.Balance{
{
Address: suite.app.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName).String(),
Coins: coins,
},
}
// update total supply
bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(sdk.NewCoin(types.DefaultEVMDenom, sdk.NewInt((int64(params.TxGas)-1)))), []banktypes.Metadata{})
bz := suite.app.AppCodec().MustMarshalJSON(bankGenesis)
require.NotNil(t, bz)
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)
// Initialize the chain
suite.app.InitChain(
abci.RequestInitChain{
ChainId: "ethermint_9000-1",
Validators: []abci.ValidatorUpdate{},
ConsensusParams: simapp.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(),
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{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
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{})
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.Marshaler
}
func (suite *KeeperTestSuite) SetupTest() {
suite.DoSetupTest(suite.T())
}
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() {
_ = 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),
})
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,
})
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),
})
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.BaseFee(suite.ctx, ethCfg)
suite.Require().Equal(tc.expectBaseFee, baseFee)
})
}
suite.enableFeemarket = false
suite.enableLondonHF = true
}
func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, &KeeperTestSuite{
enableFeemarket: false,
enableLondonHF: true,
})
}