forked from cerc-io/laconicd-deprecated
29d3abcf09
* Reuse cosmos-sdk client library to create keyring Extracted from https://github.com/evmos/ethermint/pull/1168 Cleanup cmd code for easier to migration to cosmos-sdk 0.46 * Update cosmos-sdk v0.46 prepare for implementing cosmos-sdk feemarket and tx prioritization changelog refactor cmd use sdkmath fix lint fix unit tests fix unit test genesis fix unit tests fix unit test env setup fix unit tests fix unit tests register PrivKey impl fix extension options fix lint fix unit tests make HandlerOption.Validate private gofumpt fix msg response decoding fix sim test bump cosmos-sdk version fix sim test sdk 46 fix unit test fix unit tests update ibc-go
437 lines
14 KiB
Go
437 lines
14 KiB
Go
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"
|
|
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"
|
|
"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 = sdkmath.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
|
|
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 := ð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 {
|
|
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)
|
|
|
|
// 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(),
|
|
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.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() {
|
|
_ = 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.GetBaseFee(suite.ctx, ethCfg)
|
|
suite.Require().Equal(tc.expectBaseFee, baseFee)
|
|
})
|
|
}
|
|
suite.enableFeemarket = false
|
|
suite.enableLondonHF = true
|
|
}
|