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
293 lines
9.0 KiB
Go
293 lines
9.0 KiB
Go
package keeper_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math/big"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/evmos/ethermint/app"
|
|
"github.com/evmos/ethermint/crypto/ethsecp256k1"
|
|
"github.com/evmos/ethermint/encoding"
|
|
"github.com/evmos/ethermint/tests"
|
|
"github.com/evmos/ethermint/testutil"
|
|
"github.com/evmos/ethermint/x/feemarket/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/simapp"
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
dbm "github.com/tendermint/tm-db"
|
|
)
|
|
|
|
var _ = Describe("Feemarket", func() {
|
|
var (
|
|
privKey *ethsecp256k1.PrivKey
|
|
)
|
|
|
|
Describe("Performing EVM transactions", func() {
|
|
type txParams struct {
|
|
gasLimit uint64
|
|
gasPrice *big.Int
|
|
gasFeeCap *big.Int
|
|
gasTipCap *big.Int
|
|
accesses *ethtypes.AccessList
|
|
}
|
|
type getprices func() txParams
|
|
|
|
Context("with MinGasPrices (feemarket param) < BaseFee (feemarket)", func() {
|
|
var (
|
|
baseFee int64
|
|
minGasPrices int64
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
baseFee = 10_000_000_000
|
|
minGasPrices = baseFee - 5_000_000_000
|
|
|
|
// Note that the tests run the same transactions with `gasLimit =
|
|
// 100_000`. With the fee calculation `Fee = (baseFee + tip) * gasLimit`,
|
|
// a `minGasPrices = 5_000_000_000` results in `minGlobalFee =
|
|
// 500_000_000_000_000`
|
|
privKey, _ = setupTestWithContext("1", sdk.NewDec(minGasPrices), sdk.NewInt(baseFee))
|
|
})
|
|
|
|
Context("during CheckTx", func() {
|
|
DescribeTable("should accept transactions with gas Limit > 0",
|
|
func(malleate getprices) {
|
|
p := malleate()
|
|
to := tests.GenerateAddress()
|
|
msgEthereumTx := buildEthTx(privKey, &to, p.gasLimit, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses)
|
|
res := checkEthTx(privKey, msgEthereumTx)
|
|
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())
|
|
},
|
|
Entry("legacy tx", func() txParams {
|
|
return txParams{100000, big.NewInt(baseFee), nil, nil, nil}
|
|
}),
|
|
Entry("dynamic tx", func() txParams {
|
|
return txParams{100000, nil, big.NewInt(baseFee), big.NewInt(0), ðtypes.AccessList{}}
|
|
}),
|
|
)
|
|
DescribeTable("should not accept transactions with gas Limit > 0",
|
|
func(malleate getprices) {
|
|
p := malleate()
|
|
to := tests.GenerateAddress()
|
|
msgEthereumTx := buildEthTx(privKey, &to, p.gasLimit, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses)
|
|
res := checkEthTx(privKey, msgEthereumTx)
|
|
Expect(res.IsOK()).To(Equal(false), "transaction should have succeeded", res.GetLog())
|
|
},
|
|
Entry("legacy tx", func() txParams {
|
|
return txParams{0, big.NewInt(baseFee), nil, nil, nil}
|
|
}),
|
|
Entry("dynamic tx", func() txParams {
|
|
return txParams{0, nil, big.NewInt(baseFee), big.NewInt(0), ðtypes.AccessList{}}
|
|
}),
|
|
)
|
|
})
|
|
|
|
Context("during DeliverTx", func() {
|
|
DescribeTable("should accept transactions with gas Limit > 0",
|
|
func(malleate getprices) {
|
|
p := malleate()
|
|
to := tests.GenerateAddress()
|
|
msgEthereumTx := buildEthTx(privKey, &to, p.gasLimit, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses)
|
|
res := deliverEthTx(privKey, msgEthereumTx)
|
|
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())
|
|
},
|
|
Entry("legacy tx", func() txParams {
|
|
return txParams{100000, big.NewInt(baseFee), nil, nil, nil}
|
|
}),
|
|
Entry("dynamic tx", func() txParams {
|
|
return txParams{100000, nil, big.NewInt(baseFee), big.NewInt(0), ðtypes.AccessList{}}
|
|
}),
|
|
)
|
|
DescribeTable("should not accept transactions with gas Limit > 0",
|
|
func(malleate getprices) {
|
|
p := malleate()
|
|
to := tests.GenerateAddress()
|
|
msgEthereumTx := buildEthTx(privKey, &to, p.gasLimit, p.gasPrice, p.gasFeeCap, p.gasTipCap, p.accesses)
|
|
res := checkEthTx(privKey, msgEthereumTx)
|
|
Expect(res.IsOK()).To(Equal(false), "transaction should have succeeded", res.GetLog())
|
|
},
|
|
Entry("legacy tx", func() txParams {
|
|
return txParams{0, big.NewInt(baseFee), nil, nil, nil}
|
|
}),
|
|
Entry("dynamic tx", func() txParams {
|
|
return txParams{0, nil, big.NewInt(baseFee), big.NewInt(0), ðtypes.AccessList{}}
|
|
}),
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
// setupTestWithContext sets up a test chain with an example Cosmos send msg,
|
|
// given a local (validator config) and a gloabl (feemarket param) minGasPrice
|
|
func setupTestWithContext(valMinGasPrice string, minGasPrice sdk.Dec, baseFee sdk.Int) (*ethsecp256k1.PrivKey, banktypes.MsgSend) {
|
|
privKey, msg := setupTest(valMinGasPrice + s.denom)
|
|
params := types.DefaultParams()
|
|
params.MinGasPrice = minGasPrice
|
|
s.app.FeeMarketKeeper.SetParams(s.ctx, params)
|
|
s.app.FeeMarketKeeper.SetBaseFee(s.ctx, baseFee.BigInt())
|
|
s.Commit()
|
|
|
|
return privKey, msg
|
|
}
|
|
|
|
func setupTest(localMinGasPrices string) (*ethsecp256k1.PrivKey, banktypes.MsgSend) {
|
|
setupChain(localMinGasPrices)
|
|
|
|
privKey, address := generateKey()
|
|
amount, ok := sdk.NewIntFromString("10000000000000000000")
|
|
s.Require().True(ok)
|
|
initBalance := sdk.Coins{sdk.Coin{
|
|
Denom: s.denom,
|
|
Amount: amount,
|
|
}}
|
|
testutil.FundAccount(s.app.BankKeeper, s.ctx, address, initBalance)
|
|
|
|
msg := banktypes.MsgSend{
|
|
FromAddress: address.String(),
|
|
ToAddress: address.String(),
|
|
Amount: sdk.Coins{sdk.Coin{
|
|
Denom: s.denom,
|
|
Amount: sdk.NewInt(10000),
|
|
}},
|
|
}
|
|
s.Commit()
|
|
return privKey, msg
|
|
}
|
|
|
|
func setupChain(localMinGasPricesStr string) {
|
|
// Initialize the app, so we can use SetMinGasPrices to set the
|
|
// validator-specific min-gas-prices setting
|
|
db := dbm.NewMemDB()
|
|
newapp := app.NewEthermintApp(
|
|
log.NewNopLogger(),
|
|
db,
|
|
nil,
|
|
true,
|
|
map[int64]bool{},
|
|
app.DefaultNodeHome,
|
|
5,
|
|
encoding.MakeConfig(app.ModuleBasics),
|
|
simapp.EmptyAppOptions{},
|
|
baseapp.SetMinGasPrices(localMinGasPricesStr),
|
|
)
|
|
|
|
genesisState := app.NewTestGenesisState(newapp.AppCodec())
|
|
genesisState[types.ModuleName] = newapp.AppCodec().MustMarshalJSON(types.DefaultGenesisState())
|
|
|
|
stateBytes, err := json.MarshalIndent(genesisState, "", " ")
|
|
s.Require().NoError(err)
|
|
|
|
// Initialize the chain
|
|
newapp.InitChain(
|
|
abci.RequestInitChain{
|
|
ChainId: "ethermint_9000-1",
|
|
Validators: []abci.ValidatorUpdate{},
|
|
AppStateBytes: stateBytes,
|
|
ConsensusParams: app.DefaultConsensusParams,
|
|
},
|
|
)
|
|
|
|
s.app = newapp
|
|
s.SetupApp(false)
|
|
}
|
|
|
|
func generateKey() (*ethsecp256k1.PrivKey, sdk.AccAddress) {
|
|
address, priv := tests.NewAddrKey()
|
|
return priv.(*ethsecp256k1.PrivKey), sdk.AccAddress(address.Bytes())
|
|
}
|
|
|
|
func getNonce(addressBytes []byte) uint64 {
|
|
return s.app.EvmKeeper.GetNonce(
|
|
s.ctx,
|
|
common.BytesToAddress(addressBytes),
|
|
)
|
|
}
|
|
|
|
func buildEthTx(
|
|
priv *ethsecp256k1.PrivKey,
|
|
to *common.Address,
|
|
gasLimit uint64,
|
|
gasPrice *big.Int,
|
|
gasFeeCap *big.Int,
|
|
gasTipCap *big.Int,
|
|
accesses *ethtypes.AccessList,
|
|
) *evmtypes.MsgEthereumTx {
|
|
chainID := s.app.EvmKeeper.ChainID()
|
|
from := common.BytesToAddress(priv.PubKey().Address().Bytes())
|
|
nonce := getNonce(from.Bytes())
|
|
data := make([]byte, 0)
|
|
msgEthereumTx := evmtypes.NewTx(
|
|
chainID,
|
|
nonce,
|
|
to,
|
|
nil,
|
|
gasLimit,
|
|
gasPrice,
|
|
gasFeeCap,
|
|
gasTipCap,
|
|
data,
|
|
accesses,
|
|
)
|
|
msgEthereumTx.From = from.String()
|
|
return msgEthereumTx
|
|
}
|
|
|
|
func prepareEthTx(priv *ethsecp256k1.PrivKey, msgEthereumTx *evmtypes.MsgEthereumTx) []byte {
|
|
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
|
|
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
|
|
s.Require().NoError(err)
|
|
|
|
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
|
|
builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder)
|
|
s.Require().True(ok)
|
|
builder.SetExtensionOptions(option)
|
|
|
|
err = msgEthereumTx.Sign(s.ethSigner, tests.NewSigner(priv))
|
|
s.Require().NoError(err)
|
|
|
|
// A valid msg should have empty `From`
|
|
msgEthereumTx.From = ""
|
|
err = txBuilder.SetMsgs(msgEthereumTx)
|
|
s.Require().NoError(err)
|
|
|
|
txData, err := evmtypes.UnpackTxData(msgEthereumTx.Data)
|
|
s.Require().NoError(err)
|
|
|
|
evmDenom := s.app.EvmKeeper.GetParams(s.ctx).EvmDenom
|
|
fees := sdk.Coins{{Denom: evmDenom, Amount: sdk.NewIntFromBigInt(txData.Fee())}}
|
|
builder.SetFeeAmount(fees)
|
|
builder.SetGasLimit(msgEthereumTx.GetGas())
|
|
|
|
// bz are bytes to be broadcasted over the network
|
|
bz, err := encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx())
|
|
s.Require().NoError(err)
|
|
|
|
return bz
|
|
}
|
|
|
|
func checkEthTx(priv *ethsecp256k1.PrivKey, msgEthereumTx *evmtypes.MsgEthereumTx) abci.ResponseCheckTx {
|
|
bz := prepareEthTx(priv, msgEthereumTx)
|
|
req := abci.RequestCheckTx{Tx: bz}
|
|
res := s.app.BaseApp.CheckTx(req)
|
|
return res
|
|
}
|
|
|
|
func deliverEthTx(priv *ethsecp256k1.PrivKey, msgEthereumTx *evmtypes.MsgEthereumTx) abci.ResponseDeliverTx {
|
|
bz := prepareEthTx(priv, msgEthereumTx)
|
|
req := abci.RequestDeliverTx{Tx: bz}
|
|
res := s.app.BaseApp.DeliverTx(req)
|
|
return res
|
|
}
|