forked from cerc-io/laconicd-deprecated
5e15c64d02
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.
418 lines
13 KiB
Go
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 := ðsecp256k1.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 := ðermint.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
|
|
ðtypes.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,
|
|
ðtypes.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
|
|
ðtypes.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,
|
|
})
|
|
}
|