From f106dfcc069e3dfc0caa2eaab927d8a8dbdd89a1 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Sun, 15 Sep 2024 14:05:07 +0700 Subject: [PATCH 01/14] remove the unused max-gas-wanted logic --- app/ante/evm/eth.go | 22 +++++----------------- app/ante/evm/eth_test.go | 3 +-- app/ante/handler_options.go | 3 +-- app/ante/handler_options_test.go | 2 -- app/app.go | 9 ++------- server/config/config.go | 11 ++--------- server/config/toml.go | 3 --- server/flags/flags.go | 3 +-- server/start.go | 1 - 9 files changed, 12 insertions(+), 45 deletions(-) diff --git a/app/ante/evm/eth.go b/app/ante/evm/eth.go index d77bc5e805..ddbd594b39 100644 --- a/app/ante/evm/eth.go +++ b/app/ante/evm/eth.go @@ -115,7 +115,6 @@ type EthGasConsumeDecorator struct { distributionKeeper distrkeeper.Keeper evmKeeper EVMKeeper stakingKeeper stakingkeeper.Keeper - maxGasWanted uint64 } // NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator @@ -124,14 +123,12 @@ func NewEthGasConsumeDecorator( distributionKeeper distrkeeper.Keeper, evmKeeper EVMKeeper, stakingKeeper stakingkeeper.Keeper, - maxGasWanted uint64, ) EthGasConsumeDecorator { return EthGasConsumeDecorator{ - bankKeeper, - distributionKeeper, - evmKeeper, - stakingKeeper, - maxGasWanted, + bankKeeper: bankKeeper, + distributionKeeper: distributionKeeper, + evmKeeper: evmKeeper, + stakingKeeper: stakingKeeper, } } @@ -178,16 +175,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) ethTx := msgEthTx.AsTransaction() - if ctx.IsCheckTx() && egcd.maxGasWanted != 0 { - // We can't trust the tx gas limit, because we'll refund the unused gas. - if ethTx.Gas() > egcd.maxGasWanted { - gasWanted += egcd.maxGasWanted - } else { - gasWanted += ethTx.Gas() - } - } else { - gasWanted += ethTx.Gas() - } + gasWanted += ethTx.Gas() fees, err := evmkeeper.VerifyFee(ethTx, evmDenom, baseFee, ctx.IsCheckTx()) if err != nil { diff --git a/app/ante/evm/eth_test.go b/app/ante/evm/eth_test.go index 2195286271..672448bbdd 100644 --- a/app/ante/evm/eth_test.go +++ b/app/ante/evm/eth_test.go @@ -20,7 +20,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/server/config" "github.com/EscanBE/evermint/v12/testutil" utiltx "github.com/EscanBE/evermint/v12/testutil/tx" evertypes "github.com/EscanBE/evermint/v12/types" @@ -268,7 +267,7 @@ func (suite *AnteTestSuite) TestEthGasConsumeDecorator() { suite.SetupTest() chainID := suite.app.EvmKeeper.ChainID() - dec := ethante.NewEthGasConsumeDecorator(suite.app.BankKeeper, suite.app.DistrKeeper, suite.app.EvmKeeper, *suite.app.StakingKeeper, config.DefaultMaxTxGasWanted) + dec := ethante.NewEthGasConsumeDecorator(suite.app.BankKeeper, suite.app.DistrKeeper, suite.app.EvmKeeper, *suite.app.StakingKeeper) addr := utiltx.GenerateAddress() diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 97720c04ec..920eeef4e7 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -42,7 +42,6 @@ type HandlerOptions struct { ExtensionOptionChecker ante.ExtensionOptionChecker SignModeHandler *txsigning.HandlerMap SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error - MaxTxGasWanted uint64 TxFeeChecker anteutils.TxFeeChecker DisabledAuthzMsgs map[string]bool } @@ -117,7 +116,7 @@ func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler { evmante.NewEthSigVerificationDecorator(options.EvmKeeper), evmante.NewExternalOwnedAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper), evmante.NewCanTransferDecorator(options.EvmKeeper), - evmante.NewEthGasConsumeDecorator(options.BankKeeper, *options.DistributionKeeper, options.EvmKeeper, *options.StakingKeeper, options.MaxTxGasWanted), + evmante.NewEthGasConsumeDecorator(options.BankKeeper, *options.DistributionKeeper, options.EvmKeeper, *options.StakingKeeper), evmante.NewEthIncrementSenderSequenceDecorator(options.AccountKeeper, options.EvmKeeper), evmante.NewEthSetupExecutionDecorator(options.EvmKeeper), // emit eth tx hash and index at the very last ante handler. diff --git a/app/ante/handler_options_test.go b/app/ante/handler_options_test.go index 88832ad91e..dc716cdc02 100644 --- a/app/ante/handler_options_test.go +++ b/app/ante/handler_options_test.go @@ -190,7 +190,6 @@ func (suite *AnteTestSuite) TestValidateHandlerOptions() { FeeMarketKeeper: suite.app.FeeMarketKeeper, SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), SigGasConsumer: ante.SigVerificationGasConsumer, - MaxTxGasWanted: 40000000, TxFeeChecker: ethante.NewDynamicFeeChecker(suite.app.EvmKeeper), }, expPass: false, @@ -211,7 +210,6 @@ func (suite *AnteTestSuite) TestValidateHandlerOptions() { FeeMarketKeeper: suite.app.FeeMarketKeeper, SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), SigGasConsumer: ante.SigVerificationGasConsumer, - MaxTxGasWanted: 40000000, TxFeeChecker: ethante.NewDynamicFeeChecker(suite.app.EvmKeeper), }.WithDefaultDisabledAuthzMsgs(), expPass: true, diff --git a/app/app.go b/app/app.go index dd361b1fc8..4b76ba7f53 100644 --- a/app/app.go +++ b/app/app.go @@ -56,7 +56,6 @@ import ( "github.com/EscanBE/evermint/v12/client/docs" "github.com/EscanBE/evermint/v12/constants" "github.com/EscanBE/evermint/v12/ethereum/eip712" - srvflags "github.com/EscanBE/evermint/v12/server/flags" evertypes "github.com/EscanBE/evermint/v12/types" "github.com/EscanBE/evermint/v12/utils" @@ -233,10 +232,7 @@ func NewEvermint( chainApp.MountTransientStores(chainApp.GetTransientStoreKey()) chainApp.MountMemoryStores(chainApp.GetMemoryStoreKey()) - // initialize BaseApp - maxGasWanted := cast.ToUint64(appOpts.Get(srvflags.EVMMaxTxGasWanted)) - - chainApp.setAnteHandler(txConfig, maxGasWanted) + chainApp.setAnteHandler(txConfig) chainApp.setPostHandler() chainApp.SetInitChainer(chainApp.InitChainer) @@ -380,7 +376,7 @@ func (app *Evermint) RegisterTendermintService(clientCtx client.Context) { ) } -func (app *Evermint) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint64) { +func (app *Evermint) setAnteHandler(txConfig client.TxConfig) { options := ante.HandlerOptions{ Cdc: app.appCodec, AccountKeeper: &app.AccountKeeper, @@ -395,7 +391,6 @@ func (app *Evermint) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint6 FeeMarketKeeper: app.FeeMarketKeeper, SignModeHandler: txConfig.SignModeHandler(), SigGasConsumer: ante.SigVerificationGasConsumer, - MaxTxGasWanted: maxGasWanted, TxFeeChecker: ethante.NewDynamicFeeChecker(app.EvmKeeper), }.WithDefaultDisabledAuthzMsgs() diff --git a/server/config/config.go b/server/config/config.go index e13a163ae7..64da460a55 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -33,9 +33,6 @@ const ( // DefaultEVMTracer is the default vm.Tracer type DefaultEVMTracer = "" - // DefaultMaxTxGasWanted is the default gas wanted for each eth tx returned in ante handler in check tx mode - DefaultMaxTxGasWanted = 0 - // DefaultGasCap is the default cap on gas that can be used in eth_call/estimateGas DefaultGasCap uint64 = 25000000 @@ -90,8 +87,6 @@ type EVMConfig struct { // Tracer defines vm.Tracer type that the EVM will use if the node is run in // trace mode. Default: 'json'. Tracer string `mapstructure:"tracer"` - // MaxTxGasWanted defines the gas wanted for each eth tx returned in ante handler in check tx mode. - MaxTxGasWanted uint64 `mapstructure:"max-tx-gas-wanted"` } // JSONRPCConfig defines configuration for the EVM RPC server. @@ -187,8 +182,7 @@ func DefaultConfig() *Config { // DefaultEVMConfig returns the default EVM configuration func DefaultEVMConfig() *EVMConfig { return &EVMConfig{ - Tracer: DefaultEVMTracer, - MaxTxGasWanted: DefaultMaxTxGasWanted, + Tracer: DefaultEVMTracer, } } @@ -319,8 +313,7 @@ func GetConfig(v *viper.Viper) (Config, error) { return Config{ Config: cfg, EVM: EVMConfig{ - Tracer: v.GetString("evm.tracer"), - MaxTxGasWanted: v.GetUint64("evm.max-tx-gas-wanted"), + Tracer: v.GetString("evm.tracer"), }, JSONRPC: JSONRPCConfig{ Enable: v.GetBool("json-rpc.enable"), diff --git a/server/config/toml.go b/server/config/toml.go index 1facdb6198..fe2ce92598 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -13,9 +13,6 @@ const DefaultConfigTemplate = ` # Valid types are: json|struct|access_list|markdown tracer = "{{ .EVM.Tracer }}" -# MaxTxGasWanted defines the gas wanted for each eth tx returned in ante handler in check tx mode. -max-tx-gas-wanted = {{ .EVM.MaxTxGasWanted }} - ############################################################################### ### JSON RPC Configuration ### ############################################################################### diff --git a/server/flags/flags.go b/server/flags/flags.go index e295821127..e2aea3040c 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -66,8 +66,7 @@ const ( // EVM flags const ( - EVMTracer = "evm.tracer" - EVMMaxTxGasWanted = "evm.max-tx-gas-wanted" + EVMTracer = "evm.tracer" ) // TLS flags diff --git a/server/start.go b/server/start.go index f00ae77d38..3a7e6a050c 100644 --- a/server/start.go +++ b/server/start.go @@ -199,7 +199,6 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(srvflags.JSONRPCEnableMetrics, false, "Define if EVM rpc metrics server should be enabled") cmd.Flags().String(srvflags.EVMTracer, servercfg.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") //nolint:lll - cmd.Flags().Uint64(srvflags.EVMMaxTxGasWanted, servercfg.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode") //nolint:lll cmd.Flags().String(srvflags.TLSCertPath, "", "the cert.pem file path for the server TLS configuration") cmd.Flags().String(srvflags.TLSKeyPath, "", "the key.pem file path for the server TLS configuration") From eb87c4456ab6c3ecbd3d4a045daf9f6014df62d1 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Sun, 15 Sep 2024 18:42:17 +0700 Subject: [PATCH 02/14] minor refactor to ante --- app/ante/evm/eth.go | 6 +----- app/ante/evm/setup_ctx.go | 5 ----- app/ante/handler_options.go | 32 ++++++++++++++--------------- indexer/kv_indexer.go | 14 ++----------- x/evm/keeper/fees.go | 41 +++++++++++++++++++++---------------- 5 files changed, 42 insertions(+), 56 deletions(-) diff --git a/app/ante/evm/eth.go b/app/ante/evm/eth.go index ddbd594b39..c61f91a4c6 100644 --- a/app/ante/evm/eth.go +++ b/app/ante/evm/eth.go @@ -63,7 +63,6 @@ func (avd ExternalOwnedAccountVerificationDecorator) AnteHandle( { msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - // sender address should be in the tx cache from the previous AnteHandle call from := msgEthTx.GetFrom() if from.Empty() { return ctx, errorsmod.Wrap(errortypes.ErrInvalidAddress, "from address cannot be empty") @@ -80,7 +79,7 @@ func (avd ExternalOwnedAccountVerificationDecorator) AnteHandle( } else if acct.IsContract() { return ctx, errorsmod.Wrapf( errortypes.ErrInvalidType, - "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash, + "the sender is not EOA: address %s, codeHash <%s>", fromAddr, common.BytesToHash(acct.CodeHash), ) } @@ -222,9 +221,6 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // Set tx GasMeter with a limit of GasWanted (i.e. gas limit from the Ethereum tx). // The gas consumed will be then reset to the gas used by the state transition // in the EVM. - - // FIXME: use a custom gas configuration that doesn't add any additional gas and only - // takes into account the gas consumed at the end of the EVM transaction. newCtx := ctx. WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(gasWanted)). WithPriority(minPriority) diff --git a/app/ante/evm/setup_ctx.go b/app/ante/evm/setup_ctx.go index 1a90f55052..26041e76d1 100644 --- a/app/ante/evm/setup_ctx.go +++ b/app/ante/evm/setup_ctx.go @@ -186,15 +186,10 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) err := msgEthTx.ValidateBasic() - // ErrNoSignatures is fine with eth tx if err != nil { return ctx, errorsmod.Wrap(err, "msg basic validation failed") } - if _, err := sdk.AccAddressFromBech32(msgEthTx.From); err != nil { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid From %s, expect bech32 address", msgEthTx.From) - } - ethTx := msgEthTx.AsTransaction() txGasLimit += ethTx.Gas() diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 920eeef4e7..0c1aa0af56 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -10,7 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/ante" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" @@ -38,8 +38,8 @@ type HandlerOptions struct { FeeMarketKeeper evmante.FeeMarketKeeper EvmKeeper evmante.EVMKeeper VAuthKeeper cosmosante.VAuthKeeper - FeegrantKeeper ante.FeegrantKeeper - ExtensionOptionChecker ante.ExtensionOptionChecker + FeegrantKeeper sdkauthante.FeegrantKeeper + ExtensionOptionChecker sdkauthante.ExtensionOptionChecker SignModeHandler *txsigning.HandlerMap SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error TxFeeChecker anteutils.TxFeeChecker @@ -101,7 +101,7 @@ func (options HandlerOptions) Validate() error { return nil } -// newEVMAnteHandler creates the default ante handler for Ethereum transactions +// newEVMAnteHandler creates the default AnteHandler for Ethereum transactions func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler { return sdk.ChainAnteDecorators( // outermost AnteDecorator. SetUpContext must be called first @@ -124,28 +124,28 @@ func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler { ) } -// newCosmosAnteHandler creates the default ante handler for Cosmos transactions +// newCosmosAnteHandler creates the default AnteHandler for Cosmos transactions func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { return sdk.ChainAnteDecorators( cosmosante.RejectMessagesDecorator{}, // reject MsgEthereumTxs cosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field options.DisabledAuthzMsgs, ), - ante.NewSetUpContextDecorator(), - ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), - ante.NewValidateBasicDecorator(), + sdkauthante.NewSetUpContextDecorator(), + sdkauthante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + sdkauthante.NewValidateBasicDecorator(), cosmosante.NewVestingMessagesAuthorizationDecorator(options.VAuthKeeper), - ante.NewTxTimeoutHeightDecorator(), - ante.NewValidateMemoDecorator(options.AccountKeeper), + sdkauthante.NewTxTimeoutHeightDecorator(), + sdkauthante.NewValidateMemoDecorator(options.AccountKeeper), cosmosante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), - ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + sdkauthante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), cosmosante.NewDeductFeeDecorator(*options.AccountKeeper, options.BankKeeper, *options.DistributionKeeper, options.FeegrantKeeper, *options.StakingKeeper, options.TxFeeChecker), // SetPubKeyDecorator must be called before all signature verification decorators - ante.NewSetPubKeyDecorator(options.AccountKeeper), - ante.NewValidateSigCountDecorator(options.AccountKeeper), - ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), - ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), - ante.NewIncrementSequenceDecorator(options.AccountKeeper), + sdkauthante.NewSetPubKeyDecorator(options.AccountKeeper), + sdkauthante.NewValidateSigCountDecorator(options.AccountKeeper), + sdkauthante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + sdkauthante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + sdkauthante.NewIncrementSequenceDecorator(options.AccountKeeper), ibcante.NewRedundantRelayDecorator(options.IBCKeeper), ) } diff --git a/indexer/kv_indexer.go b/indexer/kv_indexer.go index 34edf81e0c..20b8ba257f 100644 --- a/indexer/kv_indexer.go +++ b/indexer/kv_indexer.go @@ -2,10 +2,9 @@ package indexer import ( "fmt" + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" "sync" - "github.com/EscanBE/evermint/v12/constants" - errorsmod "cosmossdk.io/errors" "cosmossdk.io/log" rpctypes "github.com/EscanBE/evermint/v12/rpc/types" @@ -15,7 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/ethereum/go-ethereum/common" evertypes "github.com/EscanBE/evermint/v12/types" @@ -268,15 +266,7 @@ func LoadFirstBlock(db sdkdb.DB) (int64, error) { // isEthTx check if the tx is an eth tx func isEthTx(tx sdk.Tx) bool { - extTx, ok := tx.(authante.HasExtensionOptionsTx) - if !ok { - return false - } - opts := extTx.GetExtensionOptions() - if len(opts) != 1 || opts[0].GetTypeUrl() != constants.EthermintExtensionOptionsEthereumTx { - return false - } - return true + return dlanteutils.IsEthereumTx(tx) } // saveTxResult index the txResult into the kv db batch diff --git a/x/evm/keeper/fees.go b/x/evm/keeper/fees.go index a30b06f9f4..f4f6d1ee16 100644 --- a/x/evm/keeper/fees.go +++ b/x/evm/keeper/fees.go @@ -59,9 +59,12 @@ func (k *Keeper) DeductTxCostsFromUserBalance( return nil } -// VerifyFee is used to return the fee for the given transaction data in sdk.Coins. It checks that the -// gas limit is not reached, the gas limit is higher than the intrinsic gas and that the -// base fee is higher than the gas fee cap. +// VerifyFee is used to return the fee for the given transaction data in sdk.Coins. +// It checks: +// - Gas limit vs intrinsic gas +// - Base fee vs gas fee cap +// +// TODO ES: remove? func VerifyFee( ethTx *ethtypes.Transaction, denom string, @@ -77,22 +80,24 @@ func VerifyFee( accessList = ethTx.AccessList() } - const homestead = true - const istanbul = true - intrinsicGas, err := core.IntrinsicGas(ethTx.Data(), accessList, isContractCreation, homestead, istanbul) - if err != nil { - return nil, errorsmod.Wrapf( - err, - "failed to retrieve intrinsic gas, contract creation = %t", isContractCreation, - ) - } - // intrinsic gas verification during CheckTx - if isCheckTx && gasLimit < intrinsicGas { - return nil, errorsmod.Wrapf( - errortypes.ErrOutOfGas, - "gas limit too low: %d (gas limit) < %d (intrinsic gas)", gasLimit, intrinsicGas, - ) + if isCheckTx { + const homestead = true + const istanbul = true + intrinsicGas, err := core.IntrinsicGas(ethTx.Data(), accessList, isContractCreation, homestead, istanbul) + if err != nil { + return nil, errorsmod.Wrapf( + err, + "failed to retrieve intrinsic gas, contract creation = %t", isContractCreation, + ) + } + + if gasLimit < intrinsicGas { + return nil, errorsmod.Wrapf( + errortypes.ErrOutOfGas, + "gas limit too low: %d (gas limit) < %d (intrinsic gas)", gasLimit, intrinsicGas, + ) + } } if ethTx.GasFeeCap().Cmp(baseFee.BigInt()) < 0 { From db976574f996a56ff431bac79255e77e8a648b05 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Sun, 15 Sep 2024 20:02:04 +0700 Subject: [PATCH 03/14] add dual-lane ante --- app/antedl/ante.go | 55 +++ app/antedl/cosmoslane/991c_reject_eth_msgs.go | 38 +++ .../cosmoslane/992c_reject_authz_msgs.go | 82 +++++ .../993c_vesting_msg_authorization.go | 58 ++++ app/antedl/duallane/01_setup_ctx.go | 48 +++ app/antedl/duallane/02_ext_opt.go | 52 +++ app/antedl/duallane/03_validate_basic.go | 127 +++++++ app/antedl/duallane/04_timeout_height.go | 37 ++ app/antedl/duallane/05_memo.go | 37 ++ .../duallane/06_consume_gas_for_tx_size.go | 29 ++ app/antedl/duallane/07_deduct_fee.go | 26 ++ app/antedl/duallane/08_set_pubkey.go | 29 ++ app/antedl/duallane/09_validate_sig_count.go | 29 ++ app/antedl/duallane/10_sig_gas_consume.go | 29 ++ app/antedl/duallane/11_sig_verification.go | 148 ++++++++ .../duallane/11_sig_verification_test.go | 129 +++++++ app/antedl/duallane/12_increment_sequence.go | 60 ++++ app/antedl/duallane/13_ibc_redudant_relay.go | 30 ++ app/antedl/duallane/fee_checker.go | 228 +++++++++++++ app/antedl/duallane/fee_checker_test.go | 319 ++++++++++++++++++ app/antedl/duallane/interfaces.go | 21 ++ app/antedl/evmlane/03e_validate_basic_eoa.go | 63 ++++ app/antedl/evmlane/991e_setup_exec_ctx.go | 37 ++ app/antedl/evmlane/992e_emit_event.go | 48 +++ app/antedl/evmlane/993e_exec_without_error.go | 98 ++++++ app/antedl/handler_options.go | 107 ++++++ app/antedl/handler_options_test.go | 103 ++++++ app/antedl/setup_it_test.go | 51 +++ app/antedl/utils/tx.go | 48 +++ app/antedl/utils/tx_test.go | 276 +++++++++++++++ app/app.go | 30 +- indexer/kv_indexer.go | 12 +- integration_test_util/types/chain_app.go | 4 + .../types/chain_app_imp_keepers.go | 10 + x/feemarket/keeper/integration_test.go | 26 +- 35 files changed, 2505 insertions(+), 19 deletions(-) create mode 100644 app/antedl/ante.go create mode 100644 app/antedl/cosmoslane/991c_reject_eth_msgs.go create mode 100644 app/antedl/cosmoslane/992c_reject_authz_msgs.go create mode 100644 app/antedl/cosmoslane/993c_vesting_msg_authorization.go create mode 100644 app/antedl/duallane/01_setup_ctx.go create mode 100644 app/antedl/duallane/02_ext_opt.go create mode 100644 app/antedl/duallane/03_validate_basic.go create mode 100644 app/antedl/duallane/04_timeout_height.go create mode 100644 app/antedl/duallane/05_memo.go create mode 100644 app/antedl/duallane/06_consume_gas_for_tx_size.go create mode 100644 app/antedl/duallane/07_deduct_fee.go create mode 100644 app/antedl/duallane/08_set_pubkey.go create mode 100644 app/antedl/duallane/09_validate_sig_count.go create mode 100644 app/antedl/duallane/10_sig_gas_consume.go create mode 100644 app/antedl/duallane/11_sig_verification.go create mode 100644 app/antedl/duallane/11_sig_verification_test.go create mode 100644 app/antedl/duallane/12_increment_sequence.go create mode 100644 app/antedl/duallane/13_ibc_redudant_relay.go create mode 100644 app/antedl/duallane/fee_checker.go create mode 100644 app/antedl/duallane/fee_checker_test.go create mode 100644 app/antedl/duallane/interfaces.go create mode 100644 app/antedl/evmlane/03e_validate_basic_eoa.go create mode 100644 app/antedl/evmlane/991e_setup_exec_ctx.go create mode 100644 app/antedl/evmlane/992e_emit_event.go create mode 100644 app/antedl/evmlane/993e_exec_without_error.go create mode 100644 app/antedl/handler_options.go create mode 100644 app/antedl/handler_options_test.go create mode 100644 app/antedl/setup_it_test.go create mode 100644 app/antedl/utils/tx.go create mode 100644 app/antedl/utils/tx_test.go diff --git a/app/antedl/ante.go b/app/antedl/ante.go new file mode 100644 index 0000000000..5af5f100f7 --- /dev/null +++ b/app/antedl/ante.go @@ -0,0 +1,55 @@ +package antedl + +import ( + "github.com/EscanBE/evermint/v12/app/antedl/cosmoslane" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + "github.com/EscanBE/evermint/v12/app/antedl/evmlane" +) + +// NewAnteHandler returns an ante handler responsible for attempting to route +// an Ethereum or a Cosmos transaction to an internal ante handler for performing +// transaction-level processing (e.g. fee payment, signature verification) before +// being passed onto its respective handler. +func NewAnteHandler(options HandlerOptions) sdk.AnteHandler { + return func( + ctx sdk.Context, tx sdk.Tx, sim bool, + ) (newCtx sdk.Context, err error) { + // SDK ante plus dual-lane logic + anteDecorators := []sdk.AnteDecorator{ + duallane.NewDualLaneSetupContextDecorator(*options.EvmKeeper, sdkauthante.NewSetUpContextDecorator()), // outermost AnteDecorator. SetUpContext must be called first + duallane.NewDualLaneExtensionOptionsDecorator(sdkauthante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker)), + duallane.NewDualLaneValidateBasicDecorator(*options.EvmKeeper, sdkauthante.NewValidateBasicDecorator()), + /*EVM-only lane*/ evmlane.NewEvmLaneValidateBasicEoaDecorator(*options.AccountKeeper, *options.EvmKeeper), + duallane.NewDualLaneTxTimeoutHeightDecorator(sdkauthante.NewTxTimeoutHeightDecorator()), + duallane.NewDualLaneValidateMemoDecorator(sdkauthante.NewValidateMemoDecorator(options.AccountKeeper)), + duallane.NewDualLaneConsumeTxSizeGasDecorator(sdkauthante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper)), + duallane.NewDualLaneDeductFeeDecorator(sdkauthante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker)), + duallane.NewDualLaneSetPubKeyDecorator(sdkauthante.NewSetPubKeyDecorator(options.AccountKeeper)), // SetPubKeyDecorator must be called before all signature verification decorators + duallane.NewDualLaneValidateSigCountDecorator(sdkauthante.NewValidateSigCountDecorator(options.AccountKeeper)), + duallane.NewDualLaneSigGasConsumeDecorator(sdkauthante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer)), + duallane.NewDualLaneSigVerificationDecorator(*options.AccountKeeper, *options.EvmKeeper, sdkauthante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler)), + duallane.NewDualLaneIncrementSequenceDecorator(*options.AccountKeeper, *options.EvmKeeper, sdkauthante.NewIncrementSequenceDecorator(options.AccountKeeper)), + duallane.NewDualLaneRedundantRelayDecorator(ibcante.NewRedundantRelayDecorator(options.IBCKeeper)), + // from here, there is no longer any SDK ante + + // EVM-only lane + evmlane.NewEvmLaneSetupExecutionDecorator(*options.EvmKeeper), + evmlane.NewEvmLaneEmitEventDecorator(*options.EvmKeeper), // must be the last effective Ante + evmlane.NewEvmLaneExecWithoutErrorDecorator(*options.AccountKeeper, *options.EvmKeeper), // simulation ante + + // Cosmos-only lane + cosmoslane.NewCosmosLaneRejectEthereumMsgsDecorator(), + cosmoslane.NewCosmosLaneRejectAuthzMsgsDecorator(options.DisabledNestedMsgs), + cosmoslane.NewCosmosLaneVestingMessagesAuthorizationDecorator(*options.VAuthKeeper), + } + + anteHandler := sdk.ChainAnteDecorators(anteDecorators...) + + return anteHandler(ctx, tx, sim) + } +} diff --git a/app/antedl/cosmoslane/991c_reject_eth_msgs.go b/app/antedl/cosmoslane/991c_reject_eth_msgs.go new file mode 100644 index 0000000000..7b09247406 --- /dev/null +++ b/app/antedl/cosmoslane/991c_reject_eth_msgs.go @@ -0,0 +1,38 @@ +package cosmoslane + +import ( + errorsmod "cosmossdk.io/errors" + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type CLRejectEthereumMsgsDecorator struct{} + +// NewCosmosLaneRejectEthereumMsgsDecorator returns CLRejectEthereumMsgsDecorator, is a Cosmos-only-lane decorator. +// - If the input transaction is an Ethereum transaction, it calls next ante handler. +// - If the input transaction is a Cosmos transaction, it checks all messages should not be MsgEthereumTx. +func NewCosmosLaneRejectEthereumMsgsDecorator() CLRejectEthereumMsgsDecorator { + return CLRejectEthereumMsgsDecorator{} +} + +func (ead CLRejectEthereumMsgsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + for _, msg := range tx.GetMsgs() { + switch msg.(type) { + case *evmtypes.MsgEthereumTx: + return ctx, errorsmod.Wrapf( + sdkerrors.ErrInvalidType, + "%T cannot be mixed with Cosmos messages", (*evmtypes.MsgEthereumTx)(nil), + ) + default: + continue + } + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/cosmoslane/992c_reject_authz_msgs.go b/app/antedl/cosmoslane/992c_reject_authz_msgs.go new file mode 100644 index 0000000000..c5e49ca319 --- /dev/null +++ b/app/antedl/cosmoslane/992c_reject_authz_msgs.go @@ -0,0 +1,82 @@ +package cosmoslane + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/authz" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +// maxNestedLevelsCount defines a cap for the number of nested levels in an authz.MsgExec +const maxNestedLevelsCount = 3 + +type CLRejectAuthzMsgsDecorator struct { + disabledNestedMsgs map[string]struct{} +} + +// NewCosmosLaneRejectAuthzMsgsDecorator returns CLRejectAuthzMsgsDecorator, is a Cosmos-only-lane decorator. +// - If the input transaction is an Ethereum transaction, it calls next ante handler. +// - If the input transaction is a Cosmos transaction, it performs some verification about Authz messages. +func NewCosmosLaneRejectAuthzMsgsDecorator(disabledNestedMsgs []string) CLRejectAuthzMsgsDecorator { + d := CLRejectAuthzMsgsDecorator{ + disabledNestedMsgs: make(map[string]struct{}), + } + for _, msgType := range disabledNestedMsgs { + d.disabledNestedMsgs[msgType] = struct{}{} + } + return d +} + +func (rmd CLRejectAuthzMsgsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + if err := rmd.checkDisabledMsgs(tx.GetMsgs(), 1); err != nil { + return ctx, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, err.Error()) + } + + return next(ctx, tx, simulate) +} + +func (rmd CLRejectAuthzMsgsDecorator) checkDisabledMsgs(msgs []sdk.Msg, nestedLvl int) error { + if nestedLvl > maxNestedLevelsCount { + return errorsmod.Wrapf(sdkerrors.ErrNotSupported, "nested level: %d/%d", nestedLvl, maxNestedLevelsCount) + } + for _, msg := range msgs { + switch msg := msg.(type) { + case *authz.MsgExec: + innerMsgs, err := msg.GetMessages() + if err != nil { + return err + } + + if err := rmd.checkDisabledMsgs(innerMsgs, nestedLvl+1); err != nil { + return err + } + case *authz.MsgGrant: + authorization, err := msg.GetAuthorization() + if err != nil { + return err + } + + if url := authorization.MsgTypeURL(); rmd.isDisabledMsg(url) { + return errorsmod.Wrapf(sdkerrors.ErrNotSupported, "not allowed to grant: %s", url) + } + default: + if nestedLvl > 1 { // is nested message + if url := sdk.MsgTypeURL(msg); rmd.isDisabledMsg(url) { + return errorsmod.Wrapf(sdkerrors.ErrNotSupported, "not allowed to be nested message: %s", url) + } + } + } + } + return nil +} + +func (rmd CLRejectAuthzMsgsDecorator) isDisabledMsg(msgTypeURL string) bool { + _, found := rmd.disabledNestedMsgs[msgTypeURL] + return found +} diff --git a/app/antedl/cosmoslane/993c_vesting_msg_authorization.go b/app/antedl/cosmoslane/993c_vesting_msg_authorization.go new file mode 100644 index 0000000000..1773fb032f --- /dev/null +++ b/app/antedl/cosmoslane/993c_vesting_msg_authorization.go @@ -0,0 +1,58 @@ +package cosmoslane + +import ( + errorsmod "cosmossdk.io/errors" + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + vauthkeeper "github.com/EscanBE/evermint/v12/x/vauth/keeper" + vauthtypes "github.com/EscanBE/evermint/v12/x/vauth/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" +) + +type CLVestingMessagesAuthorizationDecorator struct { + vak vauthkeeper.Keeper +} + +// NewCosmosLaneVestingMessagesAuthorizationDecorator returns CLVestingMessagesAuthorizationDecorator, is a Cosmos-only-lane decorator. +// - If the input transaction is an Ethereum transaction, it calls next ante handler. +// - If the input transaction is a Cosmos transaction, it performs authorization for the vesting account creation messages. +// +// Rules: +// - If the target account has proof of EOA via `x/vauth`, the message can keep going. +// - Otherwise, the message will be rejected. +func NewCosmosLaneVestingMessagesAuthorizationDecorator(vak vauthkeeper.Keeper) CLVestingMessagesAuthorizationDecorator { + return CLVestingMessagesAuthorizationDecorator{ + vak: vak, + } +} + +func (vmd CLVestingMessagesAuthorizationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + for _, msg := range tx.GetMsgs() { + var account string + if m, ok := msg.(*vestingtypes.MsgCreateVestingAccount); ok { + account = m.ToAddress + } else if m, ok := msg.(*vestingtypes.MsgCreatePeriodicVestingAccount); ok { + account = m.ToAddress + } else if m, ok := msg.(*vestingtypes.MsgCreatePermanentLockedAccount); ok { + account = m.ToAddress + } else { + continue + } + + if vmd.vak.HasProofExternalOwnedAccount(ctx, sdk.MustAccAddressFromBech32(account)) { + continue + } + + return ctx, errorsmod.Wrapf( + sdkerrors.ErrUnauthorized, + "must prove account is external owned account (EOA) via `x/%s` module before able to create vesting account: %s", vauthtypes.ModuleName, account, + ) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/01_setup_ctx.go b/app/antedl/duallane/01_setup_ctx.go new file mode 100644 index 0000000000..745d178b51 --- /dev/null +++ b/app/antedl/duallane/01_setup_ctx.go @@ -0,0 +1,48 @@ +package duallane + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + "github.com/EscanBE/evermint/v12/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" +) + +type DLSetupContextDecorator struct { + ek evmkeeper.Keeper + cd sdkauthante.SetUpContextDecorator +} + +// NewDualLaneSetupContextDecorator returns DLSetupContextDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, it calls corresponding setup logic for executing Ethereum transaction. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK setup context. +func NewDualLaneSetupContextDecorator(ek evmkeeper.Keeper, cd sdkauthante.SetUpContextDecorator) DLSetupContextDecorator { + return DLSetupContextDecorator{ + ek: ek, + cd: cd, + } +} + +func (scd DLSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return scd.cd.AnteHandle(ctx, tx, simulate, next) + } + + _, ok := tx.(sdkauthante.GasTx) + if !ok { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidType, "invalid transaction type %T, expected GasTx", tx) + } + + // We need to set up an empty gas config so that the gas is consistent with Ethereum. + newCtx = ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) + newCtx = utils.UseZeroGasConfig(newCtx) + + // reset previous run + scd.ek.SetFlagSenderNonceIncreasedByAnteHandle(newCtx, false) + + return next(newCtx, tx, simulate) +} diff --git a/app/antedl/duallane/02_ext_opt.go b/app/antedl/duallane/02_ext_opt.go new file mode 100644 index 0000000000..9adb16ba88 --- /dev/null +++ b/app/antedl/duallane/02_ext_opt.go @@ -0,0 +1,52 @@ +package duallane + +import ( + errorsmod "cosmossdk.io/errors" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evertypes "github.com/EscanBE/evermint/v12/types" +) + +type DLExtensionOptionsDecorator struct { + cd sdk.AnteDecorator +} + +// NewDualLaneExtensionOptionsDecorator returns DLExtensionOptionsDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, with optional `ExtensionOptionsEthereumTx` and reject any `NonCriticalExtensionOptions`. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `ExtensionOptionsDecorator`. +func NewDualLaneExtensionOptionsDecorator(cd sdk.AnteDecorator) DLExtensionOptionsDecorator { + return DLExtensionOptionsDecorator{ + cd: cd, + } +} + +func (eod DLExtensionOptionsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return eod.cd.AnteHandle(ctx, tx, simulate, next) + } + + if !dlanteutils.IsEthereumTx(tx) { + return ctx, sdkerrors.ErrUnknownExtensionOptions + } + + wrapperTx, ok := tx.(protoTxProvider) + if !ok { + return ctx, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx) + } + + protoTx := wrapperTx.GetProtoTx() + if len(protoTx.Body.NonCriticalExtensionOptions) > 0 { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "NonCriticalExtensionOptions is now allowed") + } + + return next(ctx, tx, simulate) +} + +// OnlyAllowExtensionOptionDynamicFeeTxForCosmosTxs returns true if transaction contains `ExtensionOptionDynamicFeeTx` +func OnlyAllowExtensionOptionDynamicFeeTxForCosmosTxs(any *codectypes.Any) bool { + _, isExtensionOptionDynamicFeeTx := any.GetCachedValue().(*evertypes.ExtensionOptionDynamicFeeTx) + return isExtensionOptionDynamicFeeTx +} diff --git a/app/antedl/duallane/03_validate_basic.go b/app/antedl/duallane/03_validate_basic.go new file mode 100644 index 0000000000..25b8dbe2d7 --- /dev/null +++ b/app/antedl/duallane/03_validate_basic.go @@ -0,0 +1,127 @@ +package duallane + +import ( + "errors" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" +) + +type DLValidateBasicDecorator struct { + ek evmkeeper.Keeper + cd sdkauthante.ValidateBasicDecorator +} + +// NewDualLaneValidateBasicDecorator returns DLValidateBasicDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, call validate basic on the message. +// - If the input transaction is a Cosmos transaction, ensure no `MsgEthereumTx` is included then calls Cosmos-SDK `ValidateBasicDecorator`. +func NewDualLaneValidateBasicDecorator(ek evmkeeper.Keeper, cd sdkauthante.ValidateBasicDecorator) DLValidateBasicDecorator { + return DLValidateBasicDecorator{ + ek: ek, + cd: cd, + } +} + +func (vbd DLValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // no need to validate basic on re-check tx + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) + } + + if !dlanteutils.HasSingleEthereumMessage(tx) { + // prohibits MsgEthereumTx shipped with other messages + for _, msg := range tx.GetMsgs() { + if _, isEthMsg := msg.(*evmtypes.MsgEthereumTx); isEthMsg { + return ctx, errorsmod.Wrapf(sdkerrors.ErrLogic, "%T is not allowed to combine with other messages", (*evmtypes.MsgEthereumTx)(nil)) + } + } + + return vbd.cd.AnteHandle(ctx, tx, simulate, next) + } + + if !dlanteutils.IsEthereumTx(tx) { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "transaction has single %T but is not a valid Ethereum tx", (*evmtypes.MsgEthereumTx)(nil)) + } + + err = tx.(sdk.HasValidateBasic).ValidateBasic() + // ErrNoSignatures is fine with ETH tx, + // since we will only verify the signature against the marshalled binary embedded within the message. + if err != nil && !errors.Is(err, sdkerrors.ErrNoSignatures) { + return ctx, errorsmod.Wrap(err, "tx basic validation failed") + } + + wrapperTx, ok := tx.(protoTxProvider) + if !ok { + return ctx, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx) + } + + protoTx := wrapperTx.GetProtoTx() + // TODO ES: validate memo must empty + // TODO ES: validate timeout height must zero + + authInfo := protoTx.AuthInfo + if len(authInfo.SignerInfos) > 0 { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, AuthInfo SignerInfos should be empty") + } + if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, AuthInfo Fee payer and granter should be empty") + } + + // TODO ES: use another decorator + sigs := protoTx.Signatures + if len(sigs) > 0 { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, Signatures should be empty") + } + + msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + if err := msgEthTx.ValidateBasic(); err != nil { + return ctx, errorsmod.Wrap(err, "msg basic validation failed") + } + + evmParams := vbd.ek.GetParams(ctx) + enableCreate := evmParams.GetEnableCreate() + enableCall := evmParams.GetEnableCall() + evmDenom := evmParams.GetEvmDenom() + signer := ethtypes.LatestSignerForChainID(vbd.ek.ChainID()) + baseFee := vbd.ek.GetBaseFee(ctx) + + ethTx := msgEthTx.AsTransaction() + if _, err := ethTx.AsMessage(signer, baseFee.BigInt()); err != nil { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "cannot cast to Ethereum core message") + } + + // return error if contract creation or call are disabled through governance + if !enableCreate && ethTx.To() == nil { + return ctx, errorsmod.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") + } else if !enableCall && ethTx.To() != nil { + return ctx, errorsmod.Wrap(evmtypes.ErrCallDisabled, "failed to call contract") + } + + if !ethTx.Protected() { + return ctx, errorsmod.Wrapf(sdkerrors.ErrNotSupported, "unprotected Ethereum tx is not allowed") + } + + ethTxFee := sdk.NewCoins( + sdk.NewCoin(evmDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxFee(ethTx))), + ) + if !authInfo.Fee.Amount.Equal(ethTxFee) { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, ethTxFee) + } + + ethTxGasLimit := ethTx.Gas() + if authInfo.Fee.GasLimit != ethTxGasLimit { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, ethTxGasLimit) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/04_timeout_height.go b/app/antedl/duallane/04_timeout_height.go new file mode 100644 index 0000000000..f1f1e40da5 --- /dev/null +++ b/app/antedl/duallane/04_timeout_height.go @@ -0,0 +1,37 @@ +package duallane + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLTxTimeoutHeightDecorator struct { + cd sdkauthante.TxTimeoutHeightDecorator +} + +// NewDualLaneTxTimeoutHeightDecorator returns DLTxTimeoutHeightDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, timeout height is prohibited. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `TxTimeoutHeightDecorator`. +func NewDualLaneTxTimeoutHeightDecorator(cd sdkauthante.TxTimeoutHeightDecorator) DLTxTimeoutHeightDecorator { + return DLTxTimeoutHeightDecorator{ + cd: cd, + } +} + +func (thd DLTxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return thd.cd.AnteHandle(ctx, tx, simulate, next) + } + + wrapperTx := tx.(protoTxProvider) // was checked by validate basic + + if wrapperTx.GetProtoTx().Body.TimeoutHeight != 0 { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, TimeoutHeight should be zero") + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/05_memo.go b/app/antedl/duallane/05_memo.go new file mode 100644 index 0000000000..5d425012c6 --- /dev/null +++ b/app/antedl/duallane/05_memo.go @@ -0,0 +1,37 @@ +package duallane + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLValidateMemoDecorator struct { + cd sdkauthante.ValidateMemoDecorator +} + +// NewDualLaneValidateMemoDecorator returns DLValidateMemoDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, memo is prohibited. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `ValidateMemoDecorator`. +func NewDualLaneValidateMemoDecorator(cd sdkauthante.ValidateMemoDecorator) DLValidateMemoDecorator { + return DLValidateMemoDecorator{ + cd: cd, + } +} + +func (vmd DLValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return vmd.cd.AnteHandle(ctx, tx, simulate, next) + } + + wrapperTx := tx.(protoTxProvider) // was checked by validate basic + + if wrapperTx.GetProtoTx().Body.Memo != "" { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, memo should be empty") + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/06_consume_gas_for_tx_size.go b/app/antedl/duallane/06_consume_gas_for_tx_size.go new file mode 100644 index 0000000000..33581d7c48 --- /dev/null +++ b/app/antedl/duallane/06_consume_gas_for_tx_size.go @@ -0,0 +1,29 @@ +package duallane + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLConsumeTxSizeGasDecorator struct { + cd sdkauthante.ConsumeTxSizeGasDecorator +} + +// NewDualLaneConsumeTxSizeGasDecorator returns DLConsumeTxSizeGasDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, do nothing. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `ConsumeTxSizeGasDecorator`. +func NewDualLaneConsumeTxSizeGasDecorator(cd sdkauthante.ConsumeTxSizeGasDecorator) DLConsumeTxSizeGasDecorator { + return DLConsumeTxSizeGasDecorator{ + cd: cd, + } +} + +func (cdg DLConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return cdg.cd.AnteHandle(ctx, tx, simulate, next) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/07_deduct_fee.go b/app/antedl/duallane/07_deduct_fee.go new file mode 100644 index 0000000000..c76c918a86 --- /dev/null +++ b/app/antedl/duallane/07_deduct_fee.go @@ -0,0 +1,26 @@ +package duallane + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" +) + +type DLDeductFeeDecorator struct { + cd sdkauthante.DeductFeeDecorator +} + +// NewDualLaneDeductFeeDecorator returns DLDeductFeeDecorator, is a dual-lane decorator. +// +// It does nothing but forward to SDK DeductFeeDecorator. +// As the fee checker we are using is DualLaneFeeChecker so Ethereum Tx fee checker already included correctly. +func NewDualLaneDeductFeeDecorator( + cd sdkauthante.DeductFeeDecorator, +) DLDeductFeeDecorator { + return DLDeductFeeDecorator{ + cd: cd, + } +} + +func (dfd DLDeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + return dfd.cd.AnteHandle(ctx, tx, simulate, next) +} diff --git a/app/antedl/duallane/08_set_pubkey.go b/app/antedl/duallane/08_set_pubkey.go new file mode 100644 index 0000000000..bf53630fb4 --- /dev/null +++ b/app/antedl/duallane/08_set_pubkey.go @@ -0,0 +1,29 @@ +package duallane + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLSetPubKeyDecorator struct { + cd sdkauthante.SetPubKeyDecorator +} + +// NewDualLaneSetPubKeyDecorator returns DLSetPubKeyDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, do nothing. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `SetPubKeyDecorator`. +func NewDualLaneSetPubKeyDecorator(cd sdkauthante.SetPubKeyDecorator) DLSetPubKeyDecorator { + return DLSetPubKeyDecorator{ + cd: cd, + } +} + +func (spd DLSetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return spd.cd.AnteHandle(ctx, tx, simulate, next) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/09_validate_sig_count.go b/app/antedl/duallane/09_validate_sig_count.go new file mode 100644 index 0000000000..604ee3d5c7 --- /dev/null +++ b/app/antedl/duallane/09_validate_sig_count.go @@ -0,0 +1,29 @@ +package duallane + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLValidateSigCountDecorator struct { + cd sdkauthante.ValidateSigCountDecorator +} + +// NewDualLaneValidateSigCountDecorator returns DLValidateSigCountDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, do nothing. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `ValidateSigCountDecorator`. +func NewDualLaneValidateSigCountDecorator(cd sdkauthante.ValidateSigCountDecorator) DLValidateSigCountDecorator { + return DLValidateSigCountDecorator{ + cd: cd, + } +} + +func (vcd DLValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return vcd.cd.AnteHandle(ctx, tx, simulate, next) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/10_sig_gas_consume.go b/app/antedl/duallane/10_sig_gas_consume.go new file mode 100644 index 0000000000..1ed196f949 --- /dev/null +++ b/app/antedl/duallane/10_sig_gas_consume.go @@ -0,0 +1,29 @@ +package duallane + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLSigGasConsumeDecorator struct { + cd sdkauthante.SigGasConsumeDecorator +} + +// NewDualLaneSigGasConsumeDecorator returns DLSigGasConsumeDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, do nothing. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `SigGasConsumeDecorator`. +func NewDualLaneSigGasConsumeDecorator(cd sdkauthante.SigGasConsumeDecorator) DLSigGasConsumeDecorator { + return DLSigGasConsumeDecorator{ + cd: cd, + } +} + +func (gcd DLSigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return gcd.cd.AnteHandle(ctx, tx, simulate, next) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/11_sig_verification.go b/app/antedl/duallane/11_sig_verification.go new file mode 100644 index 0000000000..96f21f184b --- /dev/null +++ b/app/antedl/duallane/11_sig_verification.go @@ -0,0 +1,148 @@ +package duallane + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +var _ sdkauthante.SignatureVerificationGasConsumer = SigVerificationGasConsumer + +const EthSecp256k1VerifyCost uint64 = 21000 + +type DLSigVerificationDecorator struct { + ak authkeeper.AccountKeeper + ek evmkeeper.Keeper + cd sdkauthante.SigVerificationDecorator +} + +// NewDualLaneSigVerificationDecorator returns DLSigVerificationDecorator, is a dual-lane decorator. +// - If the input transaction is an Ethereum transaction, verify the signature of the inner transaction, with sender. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `SigVerificationDecorator`. +func NewDualLaneSigVerificationDecorator(ak authkeeper.AccountKeeper, ek evmkeeper.Keeper, cd sdkauthante.SigVerificationDecorator) DLSigVerificationDecorator { + return DLSigVerificationDecorator{ + ak: ak, + ek: ek, + cd: cd, + } +} + +func (svd DLSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return svd.cd.AnteHandle(ctx, tx, simulate, next) + } + + chainID := svd.ek.ChainID() + signer := ethtypes.LatestSignerForChainID(chainID) + + msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + ethTx := msgEthTx.AsTransaction() + + sender, err := signer.Sender(ethTx) + if err != nil { + return ctx, errorsmod.Wrapf( + sdkerrors.ErrorInvalidSigner, "couldn't retrieve sender address from the ethereum transaction: %s", err.Error(), + ) + } + + senderBech32 := sdk.AccAddress(sender.Bytes()).String() + if msgEthTx.From != senderBech32 { + return ctx, errorsmod.Wrapf( + sdkerrors.ErrorInvalidSigner, + "mis-match sender address: %s != %s (%s)", msgEthTx.From, senderBech32, sender.Hex(), + ) + } + + acc := svd.ak.GetAccount(ctx, msgEthTx.GetFrom()) + if acc == nil { + panic(errorsmod.Wrap(sdkerrors.ErrUnknownAddress, sender.Hex())) + } + + if ethTx.Nonce() != acc.GetSequence() { + return ctx, errorsmod.Wrapf( + sdkerrors.ErrInvalidSequence, + "invalid nonce; got %d, expected %d", ethTx.Nonce(), acc.GetSequence(), + ) + } + + return next(ctx, tx, simulate) +} + +// SigVerificationGasConsumer is this chain's implementation of SignatureVerificationGasConsumer. +// It consumes gas for signature verification based upon the public key type. +// The cost is fetched from the given params and is matched +// by the concrete type. +// The types of keys supported are: +// +// - eth_secp256k1 (Ethereum keys) +// +// - multisig (Cosmos SDK multisig) +func SigVerificationGasConsumer( + meter storetypes.GasMeter, sig signingtypes.SignatureV2, params authtypes.Params, +) error { + pubkey := sig.PubKey + switch pubkey := pubkey.(type) { + + case *ethsecp256k1.PubKey: + // Ethereum keys + meter.ConsumeGas(EthSecp256k1VerifyCost, "ante verify: eth_secp256k1") + return nil + case *ed25519.PubKey: + // Validator keys + meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported") + case multisig.PubKey: + // Multisig keys + multisig, ok := sig.Data.(*signingtypes.MultiSignatureData) + if !ok { + return fmt.Errorf("expected %T, got, %T", &signingtypes.MultiSignatureData{}, sig.Data) + } + return ConsumeMultiSignatureVerificationGas(meter, multisig, pubkey, params, sig.Sequence) + + default: + return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized/unsupported public key type: %T", pubkey) + } +} + +// ConsumeMultiSignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature +func ConsumeMultiSignatureVerificationGas( + meter storetypes.GasMeter, sig *signingtypes.MultiSignatureData, pubkey multisig.PubKey, + params authtypes.Params, accSeq uint64, +) error { + size := sig.BitArray.Count() + sigIndex := 0 + + for i := 0; i < size; i++ { + if !sig.BitArray.GetIndex(i) { + continue + } + sigV2 := signingtypes.SignatureV2{ + PubKey: pubkey.GetPubKeys()[i], + Data: sig.Signatures[sigIndex], + Sequence: accSeq, + } + err := SigVerificationGasConsumer(meter, sigV2, params) + if err != nil { + return err + } + sigIndex++ + } + + return nil +} diff --git a/app/antedl/duallane/11_sig_verification_test.go b/app/antedl/duallane/11_sig_verification_test.go new file mode 100644 index 0000000000..879e5d0240 --- /dev/null +++ b/app/antedl/duallane/11_sig_verification_test.go @@ -0,0 +1,129 @@ +package duallane_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + chainapp "github.com/EscanBE/evermint/v12/app" + "github.com/EscanBE/evermint/v12/app/ante" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" +) + +func TestConsumeSignatureVerificationGas(t *testing.T) { + generateEthSecp256k1PubKeysAndSignatures := func(n int, msg []byte) (pubKeys []cryptotypes.PubKey, signatures [][]byte) { + pubKeys = make([]cryptotypes.PubKey, n) + signatures = make([][]byte, n) + for i := 0; i < n; i++ { + var err error + + privKey, _ := ethsecp256k1.GenerateKey() + pubKeys[i] = privKey.PubKey() + signatures[i], err = privKey.Sign(msg) + require.NoError(t, err) + } + return + } + + params := authtypes.DefaultParams() + msg := []byte{1, 2, 3, 4} + + encodingConfig := chainapp.RegisterEncodingConfig() + cdc := encodingConfig.Amino + + p := authtypes.DefaultParams() + ethSecp256k1PubKeySet1, ethSecp256k1SignatureSet1 := generateEthSecp256k1PubKeysAndSignatures(5, msg) + multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, ethSecp256k1PubKeySet1) + multisig1 := multisig.NewMultisig(len(ethSecp256k1PubKeySet1)) + + for i := 0; i < len(ethSecp256k1PubKeySet1); i++ { + stdSig := legacytx.StdSignature{ + PubKey: ethSecp256k1PubKeySet1[i], + Signature: ethSecp256k1SignatureSet1[i], + } + sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig) + require.NoError(t, err) + err = multisig.AddSignatureV2(multisig1, sigV2, ethSecp256k1PubKeySet1) + require.NoError(t, err) + } + + type args struct { + meter storetypes.GasMeter + sig signing.SignatureData + pubkey cryptotypes.PubKey + params authtypes.Params + } + tests := []struct { + name string + args args + gasConsumed uint64 + wantErr bool + }{ + { + name: "fail - PubKeyEd25519", + args: args{storetypes.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, + gasConsumed: p.SigVerifyCostED25519, + wantErr: true, + }, + { + name: "pass - PubKeyEthSecp256k1", + args: args{storetypes.NewInfiniteGasMeter(), nil, ethSecp256k1PubKeySet1[0], params}, + gasConsumed: duallane.EthSecp256k1VerifyCost, + wantErr: false, + }, + { + name: "fail - PubKeySecp256k1", + args: args{storetypes.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, + gasConsumed: p.SigVerifyCostSecp256k1, + wantErr: true, + }, + { + name: "fail - PubKeySecp256r1", + args: args{storetypes.NewInfiniteGasMeter(), nil, func() cryptotypes.PubKey { + normalSecp256k1r1PrivKey, _ := secp256r1.GenPrivKey() + return normalSecp256k1r1PrivKey.PubKey() + }(), params}, + gasConsumed: p.SigVerifyCostSecp256r1(), + wantErr: true, + }, + { + name: "pass - Multisig", + args: args{storetypes.NewInfiniteGasMeter(), multisig1, multisigKey1, params}, + gasConsumed: duallane.EthSecp256k1VerifyCost * uint64(len(ethSecp256k1SignatureSet1)), + wantErr: false, + }, + { + name: "fail - unknown key", + args: args{storetypes.NewInfiniteGasMeter(), nil, nil, params}, + gasConsumed: 0, + wantErr: true, + }, + } + for _, tt := range tests { + sigV2 := signing.SignatureV2{ + PubKey: tt.args.pubkey, + Data: tt.args.sig, + Sequence: 0, // Arbitrary account sequence + } + err := ante.SigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) + + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed()) + } + } +} diff --git a/app/antedl/duallane/12_increment_sequence.go b/app/antedl/duallane/12_increment_sequence.go new file mode 100644 index 0000000000..3485ee1a86 --- /dev/null +++ b/app/antedl/duallane/12_increment_sequence.go @@ -0,0 +1,60 @@ +package duallane + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +type DLIncrementSequenceDecorator struct { + ak authkeeper.AccountKeeper + ek evmkeeper.Keeper + cd sdkauthante.IncrementSequenceDecorator +} + +// NewDualLaneIncrementSequenceDecorator returns DLIncrementSequenceDecorator, is a dual-lane decorator. +// - If the input transaction is a Cosmos transaction, it calls Cosmos-SDK `IncrementSequenceDecorator`. +// - If the input transaction is an Ethereum transaction, it increases the account nonce by one. +// +// Why? +// +// Even tho `x/evm` TransientDB already in-charges nonce increment, but since tx can be failed due to some SDK-level reason +// like panic during consume gas to block gas and the tx will be reverted, +// that's why we need an extra increment here then later revert the nonce before tx execution, at runTx step, +// so the nonce increment always be effected. +func NewDualLaneIncrementSequenceDecorator(ak authkeeper.AccountKeeper, ek evmkeeper.Keeper, cd sdkauthante.IncrementSequenceDecorator) DLIncrementSequenceDecorator { + return DLIncrementSequenceDecorator{ + ak: ak, + ek: ek, + cd: cd, + } +} + +func (svd DLIncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return svd.cd.AnteHandle(ctx, tx, simulate, next) + } + + msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + + acc := svd.ak.GetAccount(ctx, msgEthTx.GetFrom()) + if acc == nil { + // should be created when verify EOA + panic(errorsmod.Wrap(sdkerrors.ErrUnknownAddress, msgEthTx.From)) + } + + if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { + panic(err) + } + + svd.ak.SetAccount(ctx, acc) + svd.ek.SetFlagSenderNonceIncreasedByAnteHandle(ctx, true) + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/duallane/13_ibc_redudant_relay.go b/app/antedl/duallane/13_ibc_redudant_relay.go new file mode 100644 index 0000000000..79595f1eae --- /dev/null +++ b/app/antedl/duallane/13_ibc_redudant_relay.go @@ -0,0 +1,30 @@ +package duallane + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" +) + +type DLRedundantRelayDecorator struct { + cd ibcante.RedundantRelayDecorator +} + +// NewDualLaneRedundantRelayDecorator returns DLRedundantRelayDecorator, is a dual-lane decorator. +// - If the input transaction is a Cosmos transaction, it calls IBC `RedundantRelayDecorator`. +// - If the input transaction is an Ethereum transaction, in calls the next AnteHandle. +func NewDualLaneRedundantRelayDecorator(cd ibcante.RedundantRelayDecorator) DLRedundantRelayDecorator { + return DLRedundantRelayDecorator{ + cd: cd, + } +} + +func (svd DLRedundantRelayDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + return svd.cd.AnteHandle(ctx, tx, simulate, next) +} diff --git a/app/antedl/duallane/fee_checker.go b/app/antedl/duallane/fee_checker.go new file mode 100644 index 0000000000..cc4b06b753 --- /dev/null +++ b/app/antedl/duallane/fee_checker.go @@ -0,0 +1,228 @@ +package duallane + +import ( + "math" + "math/big" + + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + + cmath "github.com/ethereum/go-ethereum/common/math" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evertypes "github.com/EscanBE/evermint/v12/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" +) + +// DualLaneFeeChecker returns CosmosTxDynamicFeeChecker or EthereumTxDynamicFeeChecker based on the transaction content. +func DualLaneFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { + return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + var fc authante.TxFeeChecker + if dlanteutils.HasSingleEthereumMessage(tx) { + fc = EthereumTxDynamicFeeChecker(ek, fk) + } else { + fc = CosmosTxDynamicFeeChecker(ek, fk) + } + return fc(ctx, tx) + } +} + +// CosmosTxDynamicFeeChecker is implements `TxFeeChecker` +// that applies a dynamic fee to Cosmos txs follow EIP-1559. +func CosmosTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { + return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + if dlanteutils.HasSingleEthereumMessage(tx) { + panic("wrong call") + } + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if ctx.BlockHeight() == 0 { + // genesis transactions: fallback to min-gas-price logic + return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx) + } + + evmParams := ek.GetParams(ctx) + allowedFeeDenom := evmParams.EvmDenom + + feeMarketParams := fk.GetParams(ctx) + baseFee := feeMarketParams.BaseFee + + fees := feeTx.GetFee() + if err := validateSingleFee(fees, allowedFeeDenom); err != nil { + return nil, 0, err + } + fee := fees[0] + + var gasTipCap *sdkmath.Int + if hasExtOptsTx, ok := feeTx.(authante.HasExtensionOptionsTx); ok { + for _, opt := range hasExtOptsTx.GetExtensionOptions() { + if extOpt, ok := opt.GetCachedValue().(*evertypes.ExtensionOptionDynamicFeeTx); ok { + gasTipCap = &extOpt.MaxPriorityPrice + break + } + } + } + + var effectiveFee sdk.Coins + gas := feeTx.GetGas() + if gasTipCap != nil { // has Dynamic Fee Tx ext + // priority fee cannot be negative + if gasTipCap.IsNegative() { + return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas tip cap cannot be negative") + } + + gasFeeCap := fee.Amount.Quo(sdkmath.NewIntFromUint64(gas)) + + // Compute follow formula of Ethereum EIP-1559 + effectiveGasPrice := cmath.BigMin(new(big.Int).Add(gasTipCap.BigInt(), baseFee.BigInt()), gasFeeCap.BigInt()) + + // Dynamic Fee effective fee = effective gas price * gas + effectiveFee = sdk.NewCoins( + sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(effectiveGasPrice).Mul(sdkmath.NewIntFromUint64(gas))), + ) + } else { + // normal logic + effectiveFee = fees + } + + minGasPricesAllowed, minGasPricesSrc := getMinGasPricesAllowed(ctx, feeMarketParams, allowedFeeDenom) + priority, err := getTxPriority(effectiveFee, int64(gas), minGasPricesAllowed, minGasPricesSrc) + if err != nil { + return nil, 0, err + } + return effectiveFee, priority, nil + } +} + +// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per +// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price. +func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coins, int64, error) { + feeCoins := tx.GetFee() + minGasPrices := ctx.MinGasPrices() + gas := int64(tx.GetGas()) + + // Ensure that the provided fees meet a minimum threshold for the validator, + // if this is a CheckTx. This is only for local mempool purposes, and thus + // is only ran on check tx. + if ctx.IsCheckTx() && !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdkmath.LegacyNewDec(gas) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + if !feeCoins.IsAnyGTE(requiredFees) { + return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + } + + priority, err := getTxPriority(feeCoins, gas, sdkmath.ZeroInt(), "") + if err != nil { + return nil, 0, err + } + return feeCoins, priority, nil +} + +// EthereumTxDynamicFeeChecker is implements `TxFeeChecker`. +func EthereumTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { + return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + if !dlanteutils.HasSingleEthereumMessage(tx) || ctx.BlockHeight() == 0 { + panic("wrong call") + } + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + evmParams := ek.GetParams(ctx) + allowedFeeDenom := evmParams.EvmDenom + + feeMarketParams := fk.GetParams(ctx) + baseFee := feeMarketParams.BaseFee + + if err := validateSingleFee(feeTx.GetFee(), allowedFeeDenom); err != nil { + return nil, 0, err + } + + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + effectiveFee := sdk.NewCoins( + sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxEffectiveFee(ethTx, baseFee))), + ) + + minGasPricesAllowed, minGasPricesSrc := getMinGasPricesAllowed(ctx, feeMarketParams, allowedFeeDenom) + priority, err := getTxPriority(effectiveFee, int64(ethTx.Gas()), minGasPricesAllowed, minGasPricesSrc) + if err != nil { + return nil, 0, err + } + return effectiveFee, priority, nil + } +} + +// validateSingleFee validates if provided fee is only one type of coin, +// and denom must be exact match provided. +func validateSingleFee(fees sdk.Coins, allowedFeeDenom string) error { + if len(fees) != 1 { + return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "only one fee coin is allowed, got: %d", len(fees)) + } + fee := fees[0] + if fee.Denom != allowedFeeDenom { + return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "only '%s' is allowed as fee, got: %s", allowedFeeDenom, fee) + } + return nil +} + +// getMinGasPricesAllowed returns the biggest number among base fee and min-gas-prices of x/feemarket keeper. +// If the execution mode is check-tx (mempool), the validator min-gas-prices also included in the consideration. +func getMinGasPricesAllowed(ctx sdk.Context, fp feemarkettypes.Params, allowedFeeDenom string) (minGasPricesAllowed sdkmath.Int, minGasPricesSrc string) { + minGasPricesAllowed = fp.BaseFee + minGasPricesSrc = "base fee" + + if ctx.IsCheckTx() { // mempool + if !ctx.IsReCheckTx() { // no need to do it twice + validatorMinGasPrices := ctx.MinGasPrices().AmountOf(allowedFeeDenom).TruncateInt() + if minGasPricesAllowed.LT(validatorMinGasPrices) { + minGasPricesAllowed = validatorMinGasPrices + minGasPricesSrc = "node config" + } + } + } + + globalMinGasPrices := fp.MinGasPrice.TruncateInt() + if minGasPricesAllowed.LT(globalMinGasPrices) { + minGasPricesAllowed = globalMinGasPrices + minGasPricesSrc = "minimum global fee" + } + return +} + +// getTxPriority returns a naive tx priority based on the gas price. +// Gas price = fee / gas +func getTxPriority(fees sdk.Coins, gas int64, minGasPricesAllowed sdkmath.Int, minGasPricesSrc string) (priority int64, err error) { + fee := fees[0] // there is one and only one, validated before + priority = int64(math.MaxInt64) + + gasPrices := fee.Amount.QuoRaw(gas) + if gasPrices.LT(minGasPricesAllowed) { + err = errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas prices lower than %s, got: %s required: %s. Please retry using a higher gas price or a higher fee", minGasPricesSrc, gasPrices, minGasPricesAllowed) + return + } + + if gasPrices.IsInt64() { + priority = gasPrices.Int64() + } + + return +} diff --git a/app/antedl/duallane/fee_checker_test.go b/app/antedl/duallane/fee_checker_test.go new file mode 100644 index 0000000000..b484ce9ec3 --- /dev/null +++ b/app/antedl/duallane/fee_checker_test.go @@ -0,0 +1,319 @@ +package duallane_test + +import ( + "fmt" + "testing" + + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + + chainapp "github.com/EscanBE/evermint/v12/app" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + "github.com/EscanBE/evermint/v12/constants" + evertypes "github.com/EscanBE/evermint/v12/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +var _ duallane.EvmKeeperForFeeChecker = MockEvmKeeperForFeeChecker{} + +type MockEvmKeeperForFeeChecker struct{} + +func (m MockEvmKeeperForFeeChecker) GetParams(_ sdk.Context) evmtypes.Params { + return evmtypes.DefaultParams() +} + +var _ duallane.FeeMarketKeeperForFeeChecker = MockFeeMarketKeeperForFeeChecker{} + +type MockFeeMarketKeeperForFeeChecker struct { + BaseFee sdkmath.Int + MinGasPrices sdkmath.LegacyDec +} + +func (m MockFeeMarketKeeperForFeeChecker) GetParams(_ sdk.Context) feemarkettypes.Params { + baseFee := m.BaseFee + if baseFee.IsNil() { + baseFee = sdkmath.ZeroInt() + } + minGasPrices := m.MinGasPrices + if minGasPrices.IsNil() { + minGasPrices = sdkmath.LegacyZeroDec() + } + return feemarkettypes.Params{ + BaseFee: baseFee, + MinGasPrice: minGasPrices, + } +} + +func Test_CosmosTxDynamicFeeChecker(t *testing.T) { + encodingConfig := chainapp.RegisterEncodingConfig() + validatorMinGasPrices := sdk.NewDecCoins(sdk.NewDecCoin(constants.BaseDenom, sdkmath.NewInt(10))) + + newCtx := func(height int64, checkTx bool) sdk.Context { + return sdk.NewContext(nil, tmproto.Header{Height: height}, checkTx, log.NewNopLogger()) + } + genesisCtx := newCtx(0, false) + checkTxCtx := newCtx(1, true).WithMinGasPrices(validatorMinGasPrices) + deliverTxCtx := newCtx(1, false) + + testCases := []struct { + name string + ctx sdk.Context + keeper duallane.FeeMarketKeeperForFeeChecker + buildTx func() sdk.FeeTx + expFees string + expPriority int64 + expSuccess bool + expErrContains string + }{ + { + name: "pass - genesis tx", + ctx: genesisCtx, + keeper: MockFeeMarketKeeperForFeeChecker{}, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt()))) + return txBuilder.GetTx() + }, + expFees: "1" + constants.BaseDenom, + expPriority: 1, + expSuccess: true, + }, + { + name: "fail - no fee provided", + ctx: checkTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{}, + buildTx: func() sdk.FeeTx { + return encodingConfig.TxConfig.NewTxBuilder().GetTx() + }, + expFees: "", + expPriority: 0, + expSuccess: false, + expErrContains: "only one fee coin is allowed", + }, + { + name: "pass - min-gas-prices", + ctx: checkTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{}, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) + return txBuilder.GetTx() + }, + expFees: "10" + constants.BaseDenom, + expPriority: 10, + expSuccess: true, + }, + { + name: "pass - min-gas-prices deliverTx", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{}, + buildTx: func() sdk.FeeTx { + return encodingConfig.TxConfig.NewTxBuilder().GetTx() + }, + expFees: "", + expPriority: 0, + expSuccess: true, + }, + { + name: "fail - gas price is zero, lower than base fee", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(2), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1)))) + return txBuilder.GetTx() + }, + expFees: "", + expPriority: 0, + expSuccess: false, + expErrContains: "Please retry using a higher gas price or a higher fee", + }, + { + name: "pass - dynamic fee", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) + return txBuilder.GetTx() + }, + expFees: "10" + constants.BaseDenom, + expPriority: 10, + expSuccess: true, + }, + { + name: "fail - reject multi fee coins", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)), + sdk.NewCoin(constants.BaseDenom+"x", sdkmath.NewInt(10)), + )) + return txBuilder.GetTx() + }, + expSuccess: false, + expErrContains: "only one fee coin is allowed, got: 2", + }, + { + name: "fail - reject invalid denom fee coin", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom+"x", sdkmath.NewInt(10)), + )) + return txBuilder.GetTx() + }, + expSuccess: false, + expErrContains: fmt.Sprintf("only '%s' is allowed as fee, got:", constants.BaseDenom), + }, + { + name: "pass - dynamic fee priority", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(20)))) + return txBuilder.GetTx() + }, + expFees: "20" + constants.BaseDenom, + expPriority: 20, + expSuccess: true, + }, + { + name: "pass - dynamic fee empty tipFeeCap", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) + + option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{}) + require.NoError(t, err) + txBuilder.SetExtensionOptions(option) + return txBuilder.GetTx() + }, + expFees: "10" + constants.BaseDenom, + expPriority: 10, + expSuccess: true, + }, + { + name: "pass - dynamic fee tipFeeCap", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) + + option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ + MaxPriorityPrice: sdkmath.NewInt(5), + }) + require.NoError(t, err) + txBuilder.SetExtensionOptions(option) + return txBuilder.GetTx() + }, + expFees: "10" + constants.BaseDenom, + expPriority: 10, + expSuccess: true, + }, + { + name: "fail - negative dynamic fee tipFeeCap", + ctx: deliverTxCtx, + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(10), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(20)))) + + // set negative priority fee + option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ + MaxPriorityPrice: sdkmath.NewInt(-5), + }) + require.NoError(t, err) + txBuilder.SetExtensionOptions(option) + return txBuilder.GetTx() + }, + expFees: "", + expPriority: 0, + expSuccess: false, + expErrContains: "gas tip cap cannot be negative", + }, + { + name: "fail - low fee txs will not reach mempool due to min-gas-prices by validator", + ctx: newCtx(1, true /*check tx*/). + WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(constants.BaseDenom, sdkmath.NewInt(1e9)))), + keeper: MockFeeMarketKeeperForFeeChecker{ + BaseFee: sdkmath.NewInt(1), + }, + buildTx: func() sdk.FeeTx { + txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + txBuilder.SetGasLimit(1) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1_000_000)))) + + option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ + MaxPriorityPrice: sdkmath.NewInt(5), + }) + require.NoError(t, err) + txBuilder.SetExtensionOptions(option) + return txBuilder.GetTx() + }, + expFees: "", + expPriority: 0, + expSuccess: false, + expErrContains: "gas prices lower than node config, got: 6 required: 1000000000", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fees, priority, err := duallane.CosmosTxDynamicFeeChecker( + MockEvmKeeperForFeeChecker{}, + tc.keeper, + )(tc.ctx, tc.buildTx()) + if tc.expSuccess { + require.Equal(t, tc.expFees, fees.String()) + require.Equal(t, tc.expPriority, priority) + } else { + require.Error(t, err) + require.NotEmpty(t, tc.expErrContains, err.Error()) + require.ErrorContains(t, err, tc.expErrContains) + } + }) + } +} diff --git a/app/antedl/duallane/interfaces.go b/app/antedl/duallane/interfaces.go new file mode 100644 index 0000000000..7ed9b7f3eb --- /dev/null +++ b/app/antedl/duallane/interfaces.go @@ -0,0 +1,21 @@ +package duallane + +import ( + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdktxtypes "github.com/cosmos/cosmos-sdk/types/tx" + + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +type EvmKeeperForFeeChecker interface { + GetParams(ctx sdk.Context) evmtypes.Params +} + +type FeeMarketKeeperForFeeChecker interface { + GetParams(ctx sdk.Context) feemarkettypes.Params +} + +type protoTxProvider interface { + GetProtoTx() *sdktxtypes.Tx +} diff --git a/app/antedl/evmlane/03e_validate_basic_eoa.go b/app/antedl/evmlane/03e_validate_basic_eoa.go new file mode 100644 index 0000000000..2afe43f442 --- /dev/null +++ b/app/antedl/evmlane/03e_validate_basic_eoa.go @@ -0,0 +1,63 @@ +package evmlane + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + + "github.com/ethereum/go-ethereum/common" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +type ELValidateBasicEoaDecorator struct { + ak authkeeper.AccountKeeper + ek evmkeeper.Keeper +} + +// NewEvmLaneValidateBasicEoaDecorator returns ELValidateBasicEoaDecorator, is an EVM-only-lane decorator. +// - If the input transaction is an Ethereum transaction, it verifies the caller is an External-Owned-Account, also creates account if not exists. +// - If the input transaction is a Cosmos transaction, it calls next ante handler. +func NewEvmLaneValidateBasicEoaDecorator(ak authkeeper.AccountKeeper, ek evmkeeper.Keeper) ELValidateBasicEoaDecorator { + return ELValidateBasicEoaDecorator{ + ak: ak, + ek: ek, + } +} + +func (ead ELValidateBasicEoaDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + + from := msgEthTx.GetFrom() + if from.Empty() { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "from address cannot be empty") + } + fromAddr := common.BytesToAddress(from) + + // check whether the sender address is EOA + codeHash := ead.ek.GetCodeHash(ctx, from) + if !evmtypes.IsEmptyCodeHash(codeHash) { + return ctx, errorsmod.Wrapf( + sdkerrors.ErrInvalidType, + "the sender is not EOA: address %s, codeHash <%s>", fromAddr, codeHash, + ) + } + + // create account if not exists + acct := ead.ek.GetAccount(ctx, fromAddr) + + if acct == nil { + // TODO ES: try to not create account here to see if it works + acc := ead.ak.NewAccountWithAddress(ctx, from) + ead.ak.SetAccount(ctx, acc) + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/evmlane/991e_setup_exec_ctx.go b/app/antedl/evmlane/991e_setup_exec_ctx.go new file mode 100644 index 0000000000..295d9ecb6b --- /dev/null +++ b/app/antedl/evmlane/991e_setup_exec_ctx.go @@ -0,0 +1,37 @@ +package evmlane + +import ( + evertypes "github.com/EscanBE/evermint/v12/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +type ELSetupExecutionDecorator struct { + ek evmkeeper.Keeper +} + +// NewEvmLaneSetupExecutionDecorator creates a new ELSetupExecutionDecorator. +// - If the input transaction is an Ethereum transaction, it updates some information to transient store. +// - If the input transaction is a Cosmos transaction, it calls next ante handler. +func NewEvmLaneSetupExecutionDecorator(ek evmkeeper.Keeper) ELSetupExecutionDecorator { + return ELSetupExecutionDecorator{ + ek: ek, + } +} + +// AnteHandle emits some basic events for the eth messages +func (sed ELSetupExecutionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + sed.ek.SetupExecutionContext(ctx, ethTx.Gas(), ethTx.Type()) + + newCtx = ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(ethTx.Gas())) + + return next(newCtx, tx, simulate) +} diff --git a/app/antedl/evmlane/992e_emit_event.go b/app/antedl/evmlane/992e_emit_event.go new file mode 100644 index 0000000000..f876b5cdfc --- /dev/null +++ b/app/antedl/evmlane/992e_emit_event.go @@ -0,0 +1,48 @@ +package evmlane + +import ( + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +type ELEmitEventDecorator struct { + ek evmkeeper.Keeper +} + +// NewEvmLaneEmitEventDecorator creates a new ELEmitEventDecorator. +// - If the input transaction is an Ethereum transaction, it emits some event indicates the EVM tx is accepted and started state transition. +// - If the input transaction is a Cosmos transaction, it calls next ante handler. +func NewEvmLaneEmitEventDecorator(ek evmkeeper.Keeper) ELEmitEventDecorator { + return ELEmitEventDecorator{ + ek: ek, + } +} + +// AnteHandle emits some basic events for the eth messages +func (eed ELEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + // After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc, + // we need to emit some basic events at the very end of ante handler to be indexed by CometBFT. + txIndex := eed.ek.GetTxCountTransient(ctx) - 1 + + msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + ethTx := msgEthTx.AsTransaction() + + // emit ethereum tx hash as an event so that it can be indexed by CometBFT for query purposes + // it's emitted in ante handler, so we can query failed transaction (out of block gas limit). + ctx.EventManager().EmitEvent(sdk.NewEvent( + evmtypes.EventTypeEthereumTx, + sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, ethTx.Hash().Hex()), + sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)), + )) + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/evmlane/993e_exec_without_error.go b/app/antedl/evmlane/993e_exec_without_error.go new file mode 100644 index 0000000000..2d84f46397 --- /dev/null +++ b/app/antedl/evmlane/993e_exec_without_error.go @@ -0,0 +1,98 @@ +package evmlane + +import ( + "errors" + + errorsmod "cosmossdk.io/errors" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + "github.com/EscanBE/evermint/v12/x/evm/statedb" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +type ELExecWithoutErrorDecorator struct { + ak authkeeper.AccountKeeper + ek evmkeeper.Keeper +} + +// NewEvmLaneExecWithoutErrorDecorator creates a new ELExecWithoutErrorDecorator. +// This decorator only executes in (re)check-tx and simulation mode. +// - If the input transaction is a Cosmos transaction, it calls next ante handler. +// - If the input transaction is an Ethereum transaction, it runs simulate the state transition to ensure tx can be executed. +func NewEvmLaneExecWithoutErrorDecorator(ak authkeeper.AccountKeeper, ek evmkeeper.Keeper) ELExecWithoutErrorDecorator { + return ELExecWithoutErrorDecorator{ + ak: ak, + ek: ek, + } +} + +// AnteHandle emits some basic events for the eth messages +func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if ctx.IsCheckTx() { + // allow, re-check-tx is included + } else if simulate { + // allow + } else { + return next(ctx, tx, simulate) + } + + if !dlanteutils.HasSingleEthereumMessage(tx) { + return next(ctx, tx, simulate) + } + + baseFee := ed.ek.GetBaseFee(ctx) + signer := ethtypes.LatestSignerForChainID(ed.ek.ChainID()) + + ethMsg := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + ethTx := ethMsg.AsTransaction() + ethCoreMsg, err := ethTx.AsMessage(signer, baseFee.BigInt()) + if err != nil { + panic(err) // should be checked by basic validation + } + + { + // create a branched context for simulation + simulationCtx, _ := ctx.CacheContext() + + { // rollback the nonce which was increased by previous ante handle + acc := ed.ak.GetAccount(simulationCtx, ethMsg.GetFrom()) + err := acc.SetSequence(acc.GetSequence() - 1) + if err != nil { + panic(err) + } + ed.ak.SetAccount(simulationCtx, acc) + ed.ek.SetFlagSenderNonceIncreasedByAnteHandle(simulationCtx, false) + } + + var evm *vm.EVM + { // initialize EVM + txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(simulationCtx.HeaderHash())) + txConfig = txConfig.WithTxTypeFromMessage(ethCoreMsg) + stateDB := statedb.New(simulationCtx, &ed.ek, txConfig) + evmParams := ed.ek.GetParams(simulationCtx) + evmCfg := &statedb.EVMConfig{ + Params: evmParams, + ChainConfig: evmParams.ChainConfig.EthereumConfig(ed.ek.ChainID()), + CoinBase: common.Address{}, + BaseFee: baseFee.BigInt(), + NoBaseFee: false, + } + evm = ed.ek.NewEVM(simulationCtx, ethCoreMsg, evmCfg, evmtypes.NewNoOpTracer(), stateDB) + } + gasPool := core.GasPool(ethCoreMsg.Gas()) + _, err := evmkeeper.ApplyMessage(evm, ethCoreMsg, &gasPool) + if err != nil { + return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, err), "tx simulation execution failed") + } + } + + return next(ctx, tx, simulate) +} diff --git a/app/antedl/handler_options.go b/app/antedl/handler_options.go new file mode 100644 index 0000000000..d9c99603be --- /dev/null +++ b/app/antedl/handler_options.go @@ -0,0 +1,107 @@ +package antedl + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + txsigning "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + feemarketkeeper "github.com/EscanBE/evermint/v12/x/feemarket/keeper" + vauthkeeper "github.com/EscanBE/evermint/v12/x/vauth/keeper" + vauthtypes "github.com/EscanBE/evermint/v12/x/vauth/types" +) + +// HandlerOptions defines the list of module keepers required to run this chain +// AnteHandler decorators. +type HandlerOptions struct { + Cdc codec.BinaryCodec + AccountKeeper *authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + DistributionKeeper *distrkeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + FeegrantKeeper *feegrantkeeper.Keeper + IBCKeeper *ibckeeper.Keeper + FeeMarketKeeper *feemarketkeeper.Keeper + EvmKeeper *evmkeeper.Keeper + VAuthKeeper *vauthkeeper.Keeper + ExtensionOptionChecker sdkauthante.ExtensionOptionChecker + SignModeHandler *txsigning.HandlerMap + SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error + TxFeeChecker sdkauthante.TxFeeChecker + DisabledNestedMsgs []string // disable nested messages to be executed by `x/authz` module +} + +func (options HandlerOptions) WithDefaultDisabledNestedMsgs() HandlerOptions { + options.DisabledNestedMsgs = []string{ + sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreatePeriodicVestingAccount{}), + sdk.MsgTypeURL(&sdkvesting.MsgCreatePermanentLockedAccount{}), + } + + return options +} + +// Validate checks if the keepers are defined +func (options HandlerOptions) Validate() error { + if options.Cdc == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "codec is required for AnteHandler") + } + if options.AccountKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.DistributionKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "distribution keeper is required for AnteHandler") + } + if options.StakingKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "staking keeper is required for AnteHandler") + } + if options.FeegrantKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "staking keeper is required for AnteHandler") + } + if options.IBCKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "ibc keeper is required for AnteHandler") + } + if options.FeeMarketKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "fee market keeper is required for AnteHandler") + } + if options.EvmKeeper == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "evm keeper is required for AnteHandler") + } + if options.ExtensionOptionChecker == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "extension option checker is required for AnteHandler") + } + if options.VAuthKeeper == nil { + return errorsmod.Wrapf(sdkerrors.ErrLogic, "%s keeper is required for AnteHandler", vauthtypes.ModuleName) + } + if options.SigGasConsumer == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "signature gas consumer is required for AnteHandler") + } + if options.SignModeHandler == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for AnteHandler") + } + if options.TxFeeChecker == nil { + return errorsmod.Wrap(sdkerrors.ErrLogic, "tx fee checker is required for AnteHandler") + } + if len(options.DisabledNestedMsgs) < 1 { + return errorsmod.Wrap(sdkerrors.ErrLogic, "disabled nested msgs is required for AnteHandler") + } + return nil +} diff --git a/app/antedl/handler_options_test.go b/app/antedl/handler_options_test.go new file mode 100644 index 0000000000..3ee07900aa --- /dev/null +++ b/app/antedl/handler_options_test.go @@ -0,0 +1,103 @@ +package antedl_test + +import ( + "strconv" + "testing" + + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + "github.com/stretchr/testify/require" + + chainapp "github.com/EscanBE/evermint/v12/app" + "github.com/EscanBE/evermint/v12/app/antedl" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" + feemarketkeeper "github.com/EscanBE/evermint/v12/x/feemarket/keeper" + vauthkeeper "github.com/EscanBE/evermint/v12/x/vauth/keeper" +) + +func TestHandlerOptions_Validate(t *testing.T) { + encodingConfig := chainapp.RegisterEncodingConfig() + + initHandleOptions := func() *antedl.HandlerOptions { + options := antedl.HandlerOptions{ + Cdc: encodingConfig.Codec, + AccountKeeper: &authkeeper.AccountKeeper{}, + BankKeeper: &bankkeeper.BaseKeeper{}, + DistributionKeeper: &distrkeeper.Keeper{}, + StakingKeeper: &stakingkeeper.Keeper{}, + FeegrantKeeper: &feegrantkeeper.Keeper{}, + IBCKeeper: &ibckeeper.Keeper{}, + FeeMarketKeeper: &feemarketkeeper.Keeper{}, + EvmKeeper: &evmkeeper.Keeper{}, + VAuthKeeper: &vauthkeeper.Keeper{}, + ExtensionOptionChecker: duallane.OnlyAllowExtensionOptionDynamicFeeTxForCosmosTxs, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: duallane.SigVerificationGasConsumer, + TxFeeChecker: duallane.DualLaneFeeChecker(evmkeeper.Keeper{}, feemarketkeeper.Keeper{}), + }.WithDefaultDisabledNestedMsgs() + return &options + } + + testsMissing := []func(options *antedl.HandlerOptions){ + func(options *antedl.HandlerOptions) { + options.Cdc = nil + }, + func(options *antedl.HandlerOptions) { + options.AccountKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.BankKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.DistributionKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.StakingKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.FeegrantKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.IBCKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.FeeMarketKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.EvmKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.VAuthKeeper = nil + }, + func(options *antedl.HandlerOptions) { + options.ExtensionOptionChecker = nil + }, + func(options *antedl.HandlerOptions) { + options.SignModeHandler = nil + }, + func(options *antedl.HandlerOptions) { + options.SigGasConsumer = nil + }, + func(options *antedl.HandlerOptions) { + options.TxFeeChecker = nil + }, + func(options *antedl.HandlerOptions) { + options.DisabledNestedMsgs = nil + }, + } + for i, modifier := range testsMissing { + t.Run(strconv.Itoa(i), func(t *testing.T) { + options := initHandleOptions() + modifier(options) + + err := options.Validate() + require.Error(t, err) + require.ErrorContains(t, err, "is required") + }) + } +} diff --git a/app/antedl/setup_it_test.go b/app/antedl/setup_it_test.go new file mode 100644 index 0000000000..0894f1bc75 --- /dev/null +++ b/app/antedl/setup_it_test.go @@ -0,0 +1,51 @@ +package antedl_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" +) + +type AnteTestSuite struct { + suite.Suite + CITS *integration_test_util.ChainIntegrationTestSuite +} + +func (s *AnteTestSuite) App() itutiltypes.ChainApp { + return s.CITS.ChainApp +} + +func (s *AnteTestSuite) Ctx() sdk.Context { + return s.CITS.CurrentContext +} + +func (s *AnteTestSuite) Commit() { + s.CITS.Commit() +} + +func TestAnteTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +} + +func (s *AnteTestSuite) SetupSuite() { +} + +func (s *AnteTestSuite) SetupTest() { + s.CITS = integration_test_util.CreateChainIntegrationTestSuite(s.T(), s.Require()) +} + +func (s *AnteTestSuite) TearDownTest() { + s.CITS.Cleanup() +} + +func (s *AnteTestSuite) TearDownSuite() { +} + +func (s *AnteTestSuite) ca() itutiltypes.ChainApp { + return s.CITS.ChainApp +} diff --git a/app/antedl/utils/tx.go b/app/antedl/utils/tx.go new file mode 100644 index 0000000000..35e14c4985 --- /dev/null +++ b/app/antedl/utils/tx.go @@ -0,0 +1,48 @@ +package utils + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + "github.com/EscanBE/evermint/v12/constants" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +// HasSingleEthereumMessage returns true of the transaction is an Ethereum transaction. +func HasSingleEthereumMessage(tx sdk.Tx) bool { + var foundEthMsg bool + for _, msg := range tx.GetMsgs() { + if _, isEthMsg := msg.(*evmtypes.MsgEthereumTx); !isEthMsg { + return false + } + if foundEthMsg { + return false + } + foundEthMsg = true + } + + return foundEthMsg +} + +// IsEthereumTx returns true of the transaction is an Ethereum transaction +// and tx has no extension or has only one `ExtensionOptionsEthereumTx` +func IsEthereumTx(tx sdk.Tx) bool { + if !HasSingleEthereumMessage(tx) { + return false + } + + extTx, ok := tx.(authante.HasExtensionOptionsTx) + if !ok { + return true // allow no extension + } + + opts := extTx.GetExtensionOptions() + if len(opts) == 0 { + return true + } + if len(opts) != 1 { + return false + } + + return opts[0].GetTypeUrl() == constants.EthermintExtensionOptionsEthereumTx +} diff --git a/app/antedl/utils/tx_test.go b/app/antedl/utils/tx_test.go new file mode 100644 index 0000000000..c6ec289576 --- /dev/null +++ b/app/antedl/utils/tx_test.go @@ -0,0 +1,276 @@ +package utils_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/gogoproto/proto" + + chainapp "github.com/EscanBE/evermint/v12/app" + anteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + "github.com/EscanBE/evermint/v12/constants" + evertypes "github.com/EscanBE/evermint/v12/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +func TestIsEthereumTx(t *testing.T) { + encodingConfig := chainapp.RegisterEncodingConfig() + + txBuilder := func() client.TxBuilder { + return encodingConfig.TxConfig.NewTxBuilder() + } + + injectExtension := func(txb client.TxBuilder, exts ...proto.Message) { + var options []*codectypes.Any + for _, ext := range exts { + var option *codectypes.Any + option, err := codectypes.NewAnyWithValue(ext) + require.NoError(t, err) + options = append(options, option) + } + + builder, ok := txb.(authtx.ExtensionOptionsTxBuilder) + require.True(t, ok) + builder.SetExtensionOptions(options...) + } + + newEthMsg := func() *evmtypes.MsgEthereumTx { + ethTx := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(1), + Gas: 21000, + To: &common.Address{}, + Value: big.NewInt(0), + }) + msg := &evmtypes.MsgEthereumTx{} + err := msg.FromEthereumTx(ethTx, common.Address{}) + require.NoError(t, err) + return msg + } + + newSendMsg := func() *banktypes.MsgSend { + msg := &banktypes.MsgSend{ + FromAddress: sdk.AccAddress{}.String(), + ToAddress: sdk.AccAddress{}.String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt())), + } + return msg + } + + tests := []struct { + name string + tx func(t *testing.T) sdk.Tx + wantHasSingleEthereumMessage bool + wantIsEthereumTx bool + }{ + { + name: "pass - single Ethereum message, no ext", + tx: func(t *testing.T) sdk.Tx { + ethMsg := newEthMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg) + require.NoError(t, err) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000)))) + txb.SetGasLimit(ethMsg.GetGas()) + + return txb.GetTx() + }, + wantHasSingleEthereumMessage: true, + wantIsEthereumTx: true, + }, + { + name: "pass - single Ethereum message, with ext", + tx: func(t *testing.T) sdk.Tx { + ethMsg := newEthMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg) + require.NoError(t, err) + + injectExtension(txb, &evmtypes.ExtensionOptionsEthereumTx{}) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000)))) + txb.SetGasLimit(ethMsg.GetGas()) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: true, + wantIsEthereumTx: true, + }, + { + name: "fail - single Ethereum message, with multiple extensions", + tx: func(t *testing.T) sdk.Tx { + ethMsg := newEthMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg) + require.NoError(t, err) + + injectExtension(txb, &evmtypes.ExtensionOptionsEthereumTx{}, &evmtypes.ExtensionOptionsEthereumTx{}) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000)))) + txb.SetGasLimit(ethMsg.GetGas()) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: true, + wantIsEthereumTx: false, + }, + { + name: "fail - single Ethereum message, with invalid single extension", + tx: func(t *testing.T) sdk.Tx { + ethMsg := newEthMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg) + require.NoError(t, err) + + injectExtension(txb, &evertypes.ExtensionOptionDynamicFeeTx{}) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000)))) + txb.SetGasLimit(ethMsg.GetGas()) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: true, + wantIsEthereumTx: false, + }, + { + name: "fail - multiple Ethereum message", + tx: func(t *testing.T) sdk.Tx { + ethMsg1 := newEthMsg() + ethMsg2 := newEthMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg1, ethMsg2) + require.NoError(t, err) + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000*2)))) + txb.SetGasLimit(ethMsg1.GetGas() + ethMsg2.GetGas()) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + { + name: "fail - multiple Ethereum message, with extension", + tx: func(t *testing.T) sdk.Tx { + ethMsg1 := newEthMsg() + ethMsg2 := newEthMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg1, ethMsg2) + require.NoError(t, err) + + injectExtension(txb, &evmtypes.ExtensionOptionsEthereumTx{}) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000*2)))) + txb.SetGasLimit(ethMsg1.GetGas() + ethMsg2.GetGas()) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + { + name: "fail - Ethereum message mixed with Cosmos message", + tx: func(t *testing.T) sdk.Tx { + ethMsg1 := newEthMsg() + sendMsg2 := newSendMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg1, sendMsg2) + require.NoError(t, err) + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000+200000)))) + txb.SetGasLimit(ethMsg1.GetGas() + 200_000) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + { + name: "fail - Ethereum message mixed with Cosmos message, with extension", + tx: func(t *testing.T) sdk.Tx { + ethMsg1 := newEthMsg() + sendMsg2 := newSendMsg() + + txb := txBuilder() + err := txb.SetMsgs(ethMsg1, sendMsg2) + require.NoError(t, err) + + injectExtension(txb, &evmtypes.ExtensionOptionsEthereumTx{}) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(21000+200000)))) + txb.SetGasLimit(ethMsg1.GetGas() + 200_000) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + { + name: "fail - single Cosmos message", + tx: func(t *testing.T) sdk.Tx { + sendMsg := newSendMsg() + + txb := txBuilder() + err := txb.SetMsgs(sendMsg) + require.NoError(t, err) + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(200000)))) + txb.SetGasLimit(200_000) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + { + name: "fail - single Cosmos message, with extension", + tx: func(t *testing.T) sdk.Tx { + sendMsg := newSendMsg() + + txb := txBuilder() + err := txb.SetMsgs(sendMsg) + require.NoError(t, err) + + injectExtension(txb, &evmtypes.ExtensionOptionsEthereumTx{}) + + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(200000)))) + txb.SetGasLimit(200_000) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + { + name: "fail - multiple Cosmos messages", + tx: func(t *testing.T) sdk.Tx { + sendMsg1 := newSendMsg() + sendMsg2 := newSendMsg() + + txb := txBuilder() + err := txb.SetMsgs(sendMsg1, sendMsg2) + require.NoError(t, err) + txb.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(400000)))) + txb.SetGasLimit(400_000) + return txb.GetTx() + }, + wantHasSingleEthereumMessage: false, + wantIsEthereumTx: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hasSingleEthereumMessage := anteutils.HasSingleEthereumMessage(tt.tx(t)) + require.Equal(t, tt.wantHasSingleEthereumMessage, hasSingleEthereumMessage, "HasSingleEthereumMessage") + + isEthereumTx := anteutils.IsEthereumTx(tt.tx(t)) + require.Equal(t, tt.wantIsEthereumTx, isEthereumTx, "IsEthereumTx") + }) + } +} diff --git a/app/app.go b/app/app.go index 4b76ba7f53..f1db9872d8 100644 --- a/app/app.go +++ b/app/app.go @@ -50,6 +50,8 @@ import ( "github.com/EscanBE/evermint/v12/app/ante" ethante "github.com/EscanBE/evermint/v12/app/ante/evm" + "github.com/EscanBE/evermint/v12/app/antedl" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" "github.com/EscanBE/evermint/v12/app/keepers" "github.com/EscanBE/evermint/v12/app/params" "github.com/EscanBE/evermint/v12/app/upgrades" @@ -232,7 +234,8 @@ func NewEvermint( chainApp.MountTransientStores(chainApp.GetTransientStoreKey()) chainApp.MountMemoryStores(chainApp.GetMemoryStoreKey()) - chainApp.setAnteHandler(txConfig) + // chainApp.setAnteHandler(txConfig) + chainApp.setDualLaneAnteHandler(txConfig) chainApp.setPostHandler() chainApp.SetInitChainer(chainApp.InitChainer) @@ -401,6 +404,31 @@ func (app *Evermint) setAnteHandler(txConfig client.TxConfig) { app.SetAnteHandler(ante.NewAnteHandler(options)) } +func (app *Evermint) setDualLaneAnteHandler(txConfig client.TxConfig) { + options := antedl.HandlerOptions{ + Cdc: app.appCodec, + AccountKeeper: &app.AccountKeeper, + BankKeeper: app.BankKeeper, + ExtensionOptionChecker: evertypes.HasDynamicFeeExtensionOption, + EvmKeeper: app.EvmKeeper, + VAuthKeeper: &app.VAuthKeeper, + StakingKeeper: app.StakingKeeper, + FeegrantKeeper: &app.FeeGrantKeeper, + DistributionKeeper: &app.DistrKeeper, + IBCKeeper: app.IBCKeeper, + FeeMarketKeeper: &app.FeeMarketKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.SigVerificationGasConsumer, + TxFeeChecker: duallane.DualLaneFeeChecker(app.EvmKeeper, app.FeeMarketKeeper), + }.WithDefaultDisabledNestedMsgs() + + if err := options.Validate(); err != nil { + panic(err) + } + + app.SetAnteHandler(antedl.NewAnteHandler(options)) +} + func (app *Evermint) setPostHandler() { postHandler, err := NewPostHandler() if err != nil { diff --git a/indexer/kv_indexer.go b/indexer/kv_indexer.go index 20b8ba257f..9566773d05 100644 --- a/indexer/kv_indexer.go +++ b/indexer/kv_indexer.go @@ -2,20 +2,22 @@ package indexer import ( "fmt" - dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" "sync" - errorsmod "cosmossdk.io/errors" - "cosmossdk.io/log" - rpctypes "github.com/EscanBE/evermint/v12/rpc/types" + "github.com/ethereum/go-ethereum/common" + abci "github.com/cometbft/cometbft/abci/types" cmttypes "github.com/cometbft/cometbft/types" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" sdkdb "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + rpctypes "github.com/EscanBE/evermint/v12/rpc/types" evertypes "github.com/EscanBE/evermint/v12/types" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" ) diff --git a/integration_test_util/types/chain_app.go b/integration_test_util/types/chain_app.go index d3f1fe17c7..7e062268d3 100644 --- a/integration_test_util/types/chain_app.go +++ b/integration_test_util/types/chain_app.go @@ -2,9 +2,11 @@ package types //goland:noinspection SpellCheckingInspection import ( + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" erc20keeper "github.com/EscanBE/evermint/v12/x/erc20/keeper" evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" feemarketkeeper "github.com/EscanBE/evermint/v12/x/feemarket/keeper" + vauthkeeper "github.com/EscanBE/evermint/v12/x/vauth/keeper" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/baseapp" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -41,6 +43,8 @@ type ChainApp interface { IbcKeeper() *ibckeeper.Keeper SlashingKeeper() *slashingkeeper.Keeper StakingKeeper() *stakingkeeper.Keeper + FeeGrantKeeper() *feegrantkeeper.Keeper + VAuthKeeper() *vauthkeeper.Keeper // Tx diff --git a/integration_test_util/types/chain_app_imp_keepers.go b/integration_test_util/types/chain_app_imp_keepers.go index f67278feb5..52370ed6df 100644 --- a/integration_test_util/types/chain_app_imp_keepers.go +++ b/integration_test_util/types/chain_app_imp_keepers.go @@ -2,9 +2,11 @@ package types //goland:noinspection SpellCheckingInspection import ( + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" erc20keeper "github.com/EscanBE/evermint/v12/x/erc20/keeper" evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" feemarketkeeper "github.com/EscanBE/evermint/v12/x/feemarket/keeper" + vauthkeeper "github.com/EscanBE/evermint/v12/x/vauth/keeper" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" distkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" @@ -58,3 +60,11 @@ func (c chainAppImp) SlashingKeeper() *slashingkeeper.Keeper { func (c chainAppImp) StakingKeeper() *stakingkeeper.Keeper { return c.app.StakingKeeper } + +func (c chainAppImp) FeeGrantKeeper() *feegrantkeeper.Keeper { + return &c.app.FeeGrantKeeper +} + +func (c chainAppImp) VAuthKeeper() *vauthkeeper.Keeper { + return &c.app.VAuthKeeper +} diff --git a/x/feemarket/keeper/integration_test.go b/x/feemarket/keeper/integration_test.go index 6228aacd53..bee8b20187 100644 --- a/x/feemarket/keeper/integration_test.go +++ b/x/feemarket/keeper/integration_test.go @@ -34,7 +34,7 @@ var _ = Describe("Feemarket", func() { _, err := testutil.CheckTx(s.ctx, s.app, privKey, &gasPrice, &msg) Expect(err).ToNot(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than minimum global fee"), ).To(BeTrue(), err.Error()) }) @@ -52,7 +52,7 @@ var _ = Describe("Feemarket", func() { _, _, err := testutil.DeliverTx(s.ctx, s.app, privKey, &gasPrice, &msg) Expect(err).NotTo(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than minimum global fee"), ).To(BeTrue(), err.Error()) }) @@ -95,7 +95,7 @@ var _ = Describe("Feemarket", func() { _, _, err := testutil.DeliverTx(s.ctx, s.app, privKey, &gasPrice, &msg) Expect(err).NotTo(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than minimum global fee"), ).To(BeTrue(), err.Error()) }) @@ -111,12 +111,12 @@ var _ = Describe("Feemarket", func() { Context("with MinGasPrices (feemarket param) < min-gas-prices (local)", func() { BeforeEach(func() { - privKey, msg = setupTestWithContext("5", sdkmath.LegacyNewDec(3), sdkmath.NewInt(5)) + privKey, msg = setupTestWithContext("5", sdkmath.LegacyNewDec(3), sdkmath.NewInt(4)) }) //nolint Context("during CheckTx", func() { - It("should reject transactions with gasPrice < MinGasPrices", func() { + It("should reject transactions with gasPrice < node config min-gas-prices", func() { gasPrice := sdkmath.NewInt(2) _, err := testutil.CheckTx(s.ctx, s.app, privKey, &gasPrice, &msg) Expect(err).ToNot(BeNil(), "transaction should have failed") @@ -144,12 +144,12 @@ var _ = Describe("Feemarket", func() { //nolint Context("during DeliverTx", func() { - It("should reject transactions with gasPrice < MinGasPrices", func() { + It("should reject transactions with gasPrice < base fee < node config min-gas-prices", func() { gasPrice := sdkmath.NewInt(2) _, _, err := testutil.DeliverTx(s.ctx, s.app, privKey, &gasPrice, &msg) Expect(err).NotTo(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than base fee"), ).To(BeTrue(), err.Error()) }) @@ -206,7 +206,7 @@ var _ = Describe("Feemarket", func() { _, err := testutil.CheckEthTx(s.app, privKey, msgEthereumTx) Expect(err).ToNot(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than minimum global fee"), ).To(BeTrue(), err.Error()) }, Entry("legacy tx", func() txParams { @@ -284,7 +284,7 @@ var _ = Describe("Feemarket", func() { _, _, err := testutil.DeliverEthTx(s.ctx, s.app, privKey, msgEthereumTx) Expect(err).ToNot(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than minimum global fee"), ).To(BeTrue(), err.Error()) }, Entry("legacy tx", func() txParams { @@ -362,7 +362,7 @@ var _ = Describe("Feemarket", func() { }) Context("during CheckTx", func() { - DescribeTable("should reject transactions with gasPrice < MinGasPrices", + DescribeTable("should reject transactions with gasPrice < base fee", func(malleate getprices) { p := malleate() to := utiltx.GenerateAddress() @@ -370,7 +370,7 @@ var _ = Describe("Feemarket", func() { _, err := testutil.CheckEthTx(s.app, privKey, msgEthereumTx) Expect(err).ToNot(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than base fee"), ).To(BeTrue(), err.Error()) }, Entry("legacy tx", func() txParams { @@ -457,7 +457,7 @@ var _ = Describe("Feemarket", func() { }) Context("during DeliverTx", func() { - DescribeTable("should reject transactions with gasPrice < MinGasPrices", + DescribeTable("should reject transactions with gasPrice < base fee", func(malleate getprices) { p := malleate() to := utiltx.GenerateAddress() @@ -465,7 +465,7 @@ var _ = Describe("Feemarket", func() { _, _, err := testutil.DeliverEthTx(s.ctx, s.app, privKey, msgEthereumTx) Expect(err).ToNot(BeNil(), "transaction should have failed") Expect( - strings.Contains(err.Error(), "provided fee < minimum global fee"), + strings.Contains(err.Error(), "gas prices lower than base fee"), ).To(BeTrue(), err.Error()) }, Entry("legacy tx", func() txParams { From 336a03b221bcdc283cabdf91ff102ebc48b76911 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 16 Sep 2024 05:11:10 +0700 Subject: [PATCH 04/14] on dev Dual Lane AnteHandle IT --- app/antedl/duallane/01_setup_ctx_it_test.go | 186 ++++++++ app/antedl/duallane/02_ext_opt.go | 11 - app/antedl/duallane/02_ext_opt_it_test.go | 307 +++++++++++++ app/antedl/duallane/03_validate_basic.go | 3 - .../duallane/03_validate_basic_it_test.go | 407 ++++++++++++++++++ .../duallane/04_timeout_height_it_test.go | 105 +++++ app/antedl/duallane/05_memo_it_test.go | 109 +++++ .../06_consume_gas_for_tx_size_it_test.go | 75 ++++ app/antedl/duallane/07_deduct_fee_it_test.go | 325 ++++++++++++++ app/antedl/duallane/08_set_pubkey_it_test.go | 100 +++++ .../duallane/09_validate_sig_count_it_test.go | 80 ++++ .../duallane/10_sig_gas_consume_it_test.go | 76 ++++ .../duallane/11_sig_verification_it_test.go | 103 +++++ .../duallane/12_increment_sequence_it_test.go | 111 +++++ app/antedl/duallane/fee_checker.go | 2 + app/antedl/duallane/setup_it_test.go | 90 ++++ app/antedl/evmlane/993e_exec_without_error.go | 62 ++- app/antedl/setup_it_test.go | 51 --- app/antedl/utils/tx.go | 4 + integration_test_util/ante_suite.go | 111 +++++ integration_test_util/chain_suite_tx.go | 105 ++++- integration_test_util/types/accounts.go | 5 + integration_test_util/types/ante_test_spec.go | 129 ++++++ integration_test_util/types/tx_builder.go | 160 +++++++ 24 files changed, 2610 insertions(+), 107 deletions(-) create mode 100644 app/antedl/duallane/01_setup_ctx_it_test.go create mode 100644 app/antedl/duallane/02_ext_opt_it_test.go create mode 100644 app/antedl/duallane/03_validate_basic_it_test.go create mode 100644 app/antedl/duallane/04_timeout_height_it_test.go create mode 100644 app/antedl/duallane/05_memo_it_test.go create mode 100644 app/antedl/duallane/06_consume_gas_for_tx_size_it_test.go create mode 100644 app/antedl/duallane/07_deduct_fee_it_test.go create mode 100644 app/antedl/duallane/08_set_pubkey_it_test.go create mode 100644 app/antedl/duallane/09_validate_sig_count_it_test.go create mode 100644 app/antedl/duallane/10_sig_gas_consume_it_test.go create mode 100644 app/antedl/duallane/11_sig_verification_it_test.go create mode 100644 app/antedl/duallane/12_increment_sequence_it_test.go create mode 100644 app/antedl/duallane/setup_it_test.go delete mode 100644 app/antedl/setup_it_test.go create mode 100644 integration_test_util/ante_suite.go create mode 100644 integration_test_util/types/ante_test_spec.go create mode 100644 integration_test_util/types/tx_builder.go diff --git a/app/antedl/duallane/01_setup_ctx_it_test.go b/app/antedl/duallane/01_setup_ctx_it_test.go new file mode 100644 index 0000000000..0d47d3ad83 --- /dev/null +++ b/app/antedl/duallane/01_setup_ctx_it_test.go @@ -0,0 +1,186 @@ +package duallane_test + +import ( + "math" + "math/big" + + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/constants" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + storetypes "cosmossdk.io/store/types" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLSetupContextDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + onSuccess func(ctx sdk.Context, tx sdk.Tx) + }{ + { + name: "pass - single-ETH - setup correctly", + tx: func(ctx sdk.Context) sdk.Tx { + s.App().EvmKeeper().SetFlagSenderNonceIncreasedByAnteHandle(ctx, true) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(21_000, int(ctx.GasMeter().Limit()), "gas meter should be set to tx gas by another decorator") + s.Equal(uint64(math.MaxUint64), ctx.GasMeter().GasRemaining(), "gas meter should be infinite by custom infinite gas meter with limit") + s.Equal(storetypes.GasConfig{}, ctx.KVGasConfig()) + s.Equal(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) + + s.True(s.App().EvmKeeper().IsSenderNonceIncreasedByAnteHandle(ctx), "this flag should be set by another decorator") + }), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(uint64(math.MaxUint64), ctx.GasMeter().Limit(), "this decorator should use infinite gas meter") + s.Equal(uint64(math.MaxUint64), ctx.GasMeter().GasRemaining(), "this decorator should use infinite gas meter") + s.Equal(storetypes.GasConfig{}, ctx.KVGasConfig()) + s.Equal(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) + + s.False(s.App().EvmKeeper().IsSenderNonceIncreasedByAnteHandle(ctx)) + }), + }, + { + name: "pass - single-Cosmos - setup correctly", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(500_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + s.NotEqual(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - multi-Cosmos - setup correctly", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(1_500_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - Multi-ETH - should not run setup for ETH", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg2 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 1, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + return s.TxB().SetMsgs(ethMsg1, ethMsg2).AutoGasLimit().AutoFee().Tx() + }, + anteSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + + s.Equal(42_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts().WantsSuccess().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + // should success because no validation performed at this decorator + + // follow Cosmos-lane rules + s.Equal(42_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + { + name: "fail - Multi-ETH mixed Cosmos - should not run setup for ETH", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + cosmosMsg2 := &banktypes.MsgSend{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: acc2.GetCosmosAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt())), + } + + return s.TxB().SetMsgs(ethMsg1, cosmosMsg2).SetGasLimit(521_000).BigFeeAmount(1).Tx() + }, + anteSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + + s.Equal(521_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts().WantsSuccess().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + // should success because no validation performed at this decorator + + // follow Cosmos-lane rules + s.Equal(521_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneSetupContextDecorator(*s.App().EvmKeeper(), sdkauthante.NewSetUpContextDecorator()), + ) + + if tt.onSuccess != nil { + tt.anteSpec.OnSuccess(tt.onSuccess) + tt.decoratorSpec.OnSuccess(tt.onSuccess) + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/02_ext_opt.go b/app/antedl/duallane/02_ext_opt.go index 9adb16ba88..b21bb86bd7 100644 --- a/app/antedl/duallane/02_ext_opt.go +++ b/app/antedl/duallane/02_ext_opt.go @@ -1,7 +1,6 @@ package duallane import ( - errorsmod "cosmossdk.io/errors" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -32,16 +31,6 @@ func (eod DLExtensionOptionsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, si return ctx, sdkerrors.ErrUnknownExtensionOptions } - wrapperTx, ok := tx.(protoTxProvider) - if !ok { - return ctx, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx) - } - - protoTx := wrapperTx.GetProtoTx() - if len(protoTx.Body.NonCriticalExtensionOptions) > 0 { - return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "NonCriticalExtensionOptions is now allowed") - } - return next(ctx, tx, simulate) } diff --git a/app/antedl/duallane/02_ext_opt_it_test.go b/app/antedl/duallane/02_ext_opt_it_test.go new file mode 100644 index 0000000000..dc1c330679 --- /dev/null +++ b/app/antedl/duallane/02_ext_opt_it_test.go @@ -0,0 +1,307 @@ +package duallane_test + +import ( + "math/big" + + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/constants" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + evertypes "github.com/EscanBE/evermint/v12/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + storetypes "cosmossdk.io/store/types" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLExtensionOptionsDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + onSuccess func(ctx sdk.Context, tx sdk.Tx) + }{ + { + name: "pass - single-ETH - without extension", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB() + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-ETH - with extension", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetExtensionOptions(&evmtypes.ExtensionOptionsEthereumTx{}) + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-ETH - with another extension", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + }, + { + name: "pass - single-Cosmos - without extension", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-Cosmos - with ExtensionOptionDynamicFeeTx", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - with another extension", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evmtypes.ExtensionOptionsEthereumTx{}) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + }, + { + name: "pass - multi-Cosmos - without ext", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }, + }, + { + name: "pass - multi-Cosmos - with ExtensionOptionDynamicFeeTx", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }, + }, + { + name: "fail - multi-Cosmos - with unknown extension", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evmtypes.ExtensionOptionsEthereumTx{}) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + }, + { + name: "fail - Multi-ETH - with ExtensionOptionsEthereumTx, should be rejected by Cosmos flow", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg2 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 1, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + tb := s.TxB() + tb.SetMsgs(ethMsg1, ethMsg2).AutoGasLimit().AutoFee().Tx() + tb.SetExtensionOptions(&evmtypes.ExtensionOptionsEthereumTx{}) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + }, + { + name: "fail Ante/pass Decorator - Multi-ETH - with ExtensionOptionDynamicFeeTx, should be rejected by Cosmos flow", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg2 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 1, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + tb := s.TxB() + tb.SetMsgs(ethMsg1, ethMsg2).AutoGasLimit().AutoFee().Tx() + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) + return tb.Tx() + }, + anteSpec: ts().WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - Multi-ETH mixed Cosmos - with ExtensionOptionsEthereumTx, should be rejected by Cosmos flow", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + cosmosMsg2 := &banktypes.MsgSend{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: acc2.GetCosmosAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt())), + } + + tb := s.TxB().SetMsgs(ethMsg1, cosmosMsg2).SetGasLimit(521_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evmtypes.ExtensionOptionsEthereumTx{}) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrUnknownExtensionOptions.Error()), + }, + { + name: "fail Ante/pass Decorator - Multi-ETH mixed Cosmos - with ExtensionOptionDynamicFeeTx, should be rejected by Cosmos flow", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + cosmosMsg2 := &banktypes.MsgSend{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: acc2.GetCosmosAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt())), + } + + tb := s.TxB().SetMsgs(ethMsg1, cosmosMsg2).SetGasLimit(521_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) + return tb.Tx() + }, + anteSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts().WantsSuccess().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + // should success because no validation performed at this decorator + + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneExtensionOptionsDecorator(sdkauthante.NewExtensionOptionsDecorator(s.ATS.HandlerOptions.ExtensionOptionChecker)), + ) + + if tt.onSuccess != nil { + tt.anteSpec.OnSuccess(tt.onSuccess) + tt.decoratorSpec.OnSuccess(tt.onSuccess) + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/03_validate_basic.go b/app/antedl/duallane/03_validate_basic.go index 25b8dbe2d7..1cfd2f28d5 100644 --- a/app/antedl/duallane/03_validate_basic.go +++ b/app/antedl/duallane/03_validate_basic.go @@ -66,8 +66,6 @@ func (vbd DLValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } protoTx := wrapperTx.GetProtoTx() - // TODO ES: validate memo must empty - // TODO ES: validate timeout height must zero authInfo := protoTx.AuthInfo if len(authInfo.SignerInfos) > 0 { @@ -100,7 +98,6 @@ func (vbd DLValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "cannot cast to Ethereum core message") } - // return error if contract creation or call are disabled through governance if !enableCreate && ethTx.To() == nil { return ctx, errorsmod.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") } else if !enableCall && ethTx.To() != nil { diff --git a/app/antedl/duallane/03_validate_basic_it_test.go b/app/antedl/duallane/03_validate_basic_it_test.go new file mode 100644 index 0000000000..dda5029d1e --- /dev/null +++ b/app/antedl/duallane/03_validate_basic_it_test.go @@ -0,0 +1,407 @@ +package duallane_test + +import ( + "math/big" + + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + + "github.com/ethereum/go-ethereum/common" + + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/constants" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + storetypes "cosmossdk.io/store/types" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLValidateBasicDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - valid legacy tx", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-ETH - valid Dynamic Fee tx", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-ETH - valid access-list tx", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.AccessListTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + AccessList: ethtypes.AccessList{ + { + Address: acc1.GetEthAddress(), + StorageKeys: []common.Hash{{}}, + }, + }, + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-ETH - validate basic should be called", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg.From = "" + + return s.TxB().SetMsgs(ethMsg).AutoGasLimit().AutoFee().Tx() + }, + anteSpec: ts().WantsErrMsgContains("msg basic validation failed"), + decoratorSpec: ts().WantsErrMsgContains("msg basic validation failed"), + }, + { + name: "fail - single-ETH - tx with signature should be declined", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + tb := s.TxB().SetMsgs(ethMsg).AutoGasLimit().AutoFee() + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("for ETH txs, AuthInfo SignerInfos should be empty"), + decoratorSpec: ts().WantsErrMsgContains("for ETH txs, AuthInfo SignerInfos should be empty"), + }, + { + name: "fail - single-ETH - tx with fee payer will be denied", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + tb := s.TxB().SetMsgs(ethMsg).AutoGasLimit().AutoFee() + tb.ClientTxBuilder().SetFeePayer(acc1.GetCosmosAddress()) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("for ETH txs, AuthInfo Fee payer and granter should be empty"), + decoratorSpec: ts().WantsErrMsgContains("for ETH txs, AuthInfo Fee payer and granter should be empty"), + }, + { + name: "fail - single-ETH - tx with fee granter will be denied", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + tb := s.TxB().SetMsgs(ethMsg).AutoGasLimit().AutoFee() + tb.ClientTxBuilder().SetFeeGranter(acc1.GetCosmosAddress()) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("for ETH txs, AuthInfo Fee payer and granter should be empty"), + decoratorSpec: ts().WantsErrMsgContains("for ETH txs, AuthInfo Fee payer and granter should be empty"), + }, + { + name: "fail - single-ETH - contract creation will be declined when disabled", + tx: func(ctx sdk.Context) sdk.Tx { + evmParams := evmtypes.DefaultParams() + evmParams.EnableCreate = false + err := s.App().EvmKeeper().SetParams(ctx, evmParams) + s.Require().NoError(err) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: nil, + Value: big.NewInt(1), + Data: make([]byte, 1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("failed to create new contract"), + decoratorSpec: ts().WantsErrMsgContains("failed to create new contract"), + }, + { + name: "fail - single-ETH - send will be declined when disabled call", + tx: func(ctx sdk.Context) sdk.Tx { + evmParams := evmtypes.DefaultParams() + evmParams.EnableCall = false + err := s.App().EvmKeeper().SetParams(ctx, evmParams) + s.Require().NoError(err) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("failed to call contract"), + decoratorSpec: ts().WantsErrMsgContains("failed to call contract"), + }, + { + name: "fail - single-ETH - contract call will be declined when disabled call", + tx: func(ctx sdk.Context) sdk.Tx { + evmParams := evmtypes.DefaultParams() + evmParams.EnableCall = false + err := s.App().EvmKeeper().SetParams(ctx, evmParams) + s.Require().NoError(err) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + Data: []byte{0x1, 0x2, 0x3, 0x4}, + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("failed to call contract"), + decoratorSpec: ts().WantsErrMsgContains("failed to call contract"), + }, + { + name: "fail - single-ETH - unprotected tx will be declined", + tx: func(ctx sdk.Context) sdk.Tx { + ethTx := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + ethMsg := &evmtypes.MsgEthereumTx{} + err := ethMsg.FromEthereumTx(ethTx, acc1.GetEthAddress()) + s.Require().NoError(err) + + homesteadSigner := ethtypes.HomesteadSigner{} + err = ethMsg.Sign(homesteadSigner, itutiltypes.NewSigner(acc1.PrivateKey)) + s.Require().NoError(err) + + return s.TxB().SetMsgs(ethMsg).AutoGasLimit().AutoFee().Tx() + }, + anteSpec: ts().WantsErrMsgContains("unprotected Ethereum tx is not allowed"), + decoratorSpec: ts().WantsErrMsgContains("unprotected Ethereum tx is not allowed"), + }, + { + name: "fail - single-ETH - should reject invalid fee amount", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + Data: []byte{0x1, 0x2, 0x3, 0x4}, + }, s.TxB()) + s.Require().NoError(err) + ctb.SetFeeAmount(sdk.Coins{}) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("invalid AuthInfo Fee Amount"), + decoratorSpec: ts().WantsErrMsgContains("invalid AuthInfo Fee Amount"), + }, + { + name: "fail - single-ETH - should reject invalid gas amount", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + Data: []byte{0x1, 0x2, 0x3, 0x4}, + }, s.TxB()) + s.Require().NoError(err) + ctb.SetGasLimit(50_000) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("invalid AuthInfo Fee GasLimit"), + decoratorSpec: ts().WantsErrMsgContains("invalid AuthInfo Fee GasLimit"), + }, + { + name: "pass - single-Cosmos - valid message", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(500_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + s.NotEqual(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) + }), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + { + name: "pass - multi-Cosmos - valid message", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + ctb, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(1_500_000, int(ctx.GasMeter().Limit())) + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + s.NotEqual(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) + }), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + { + name: "fail - Multi-ETH - should be rejected by Cosmos lane", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg2 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 1, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + return s.TxB().SetMsgs(ethMsg1, ethMsg2).AutoGasLimit().AutoFee().Tx() + }, + anteSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + { + name: "fail - Multi-ETH mixed Cosmos - should be rejected by Cosmos lane", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + cosmosMsg2 := &banktypes.MsgSend{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: acc2.GetCosmosAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt())), + } + + return s.TxB().SetMsgs(ethMsg1, cosmosMsg2).SetGasLimit(521_000).BigFeeAmount(1).Tx() + }, + anteSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + decoratorSpec: ts(). + WantsErrMultiEthTx(). + OnFail(func(ctx sdk.Context, anteErr error, tx sdk.Tx) { + // follow Cosmos-lane rules + s.NotEqual(storetypes.GasConfig{}, ctx.KVGasConfig()) + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneValidateBasicDecorator(*s.App().EvmKeeper(), sdkauthante.NewValidateBasicDecorator()), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/04_timeout_height_it_test.go b/app/antedl/duallane/04_timeout_height_it_test.go new file mode 100644 index 0000000000..5e4ad173f1 --- /dev/null +++ b/app/antedl/duallane/04_timeout_height_it_test.go @@ -0,0 +1,105 @@ +package duallane_test + +import ( + "math/big" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLTxTimeoutHeightDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + const currentBlockNumber = 5 + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - valid tx without timeout height", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-ETH - should reject when timeout height is not zero", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + ctb.SetTimeoutHeight(1) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("for ETH txs, TimeoutHeight should be zero"), + decoratorSpec: ts().WantsErrMsgContains("for ETH txs, TimeoutHeight should be zero"), + }, + { + name: "pass - single-Cosmos - valid timeout height", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.ClientTxBuilder().SetTimeoutHeight(currentBlockNumber + 1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - bad timeout height", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.ClientTxBuilder().SetTimeoutHeight(currentBlockNumber - 1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrTxTimeoutHeight.Error()), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrTxTimeoutHeight.Error()), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + cachedCtx = cachedCtx.WithBlockHeight(currentBlockNumber) + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneTxTimeoutHeightDecorator(sdkauthante.NewTxTimeoutHeightDecorator()), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/05_memo_it_test.go b/app/antedl/duallane/05_memo_it_test.go new file mode 100644 index 0000000000..8e84a46057 --- /dev/null +++ b/app/antedl/duallane/05_memo_it_test.go @@ -0,0 +1,109 @@ +package duallane_test + +import ( + "math/big" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLValidateMemoDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - valid tx without memo", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-ETH - should reject when memo is not empty", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + ctb.SetMemo("memo") + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("for ETH txs, memo should be empty"), + decoratorSpec: ts().WantsErrMsgContains("for ETH txs, memo should be empty"), + }, + { + name: "pass - single-Cosmos - valid memo", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.ClientTxBuilder().SetMemo("short") + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - memo too long", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + { + maxLength := s.App().AccountKeeper().GetParams(ctx).MaxMemoCharacters + memo := make([]byte, maxLength+1) + for i := 0; i < len(memo); i++ { + memo[i] = 'a' + } + tb.ClientTxBuilder().SetMemo(string(memo)) + } + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains(sdkerrors.ErrMemoTooLarge.Error()), + decoratorSpec: ts().WantsErrMsgContains(sdkerrors.ErrMemoTooLarge.Error()), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneValidateMemoDecorator(sdkauthante.NewValidateMemoDecorator(s.App().AccountKeeper())), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/06_consume_gas_for_tx_size_it_test.go b/app/antedl/duallane/06_consume_gas_for_tx_size_it_test.go new file mode 100644 index 0000000000..e9ee181c58 --- /dev/null +++ b/app/antedl/duallane/06_consume_gas_for_tx_size_it_test.go @@ -0,0 +1,75 @@ +package duallane_test + +import ( + "math/big" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLConsumeTxSizeGasDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - will not consume gas", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(uint64(0), ctx.GasMeter().GasConsumed(), "no gas should be consumed") + }), + }, + { + name: "pass - single-Cosmos - will consume gas", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Less(uint64(0), ctx.GasMeter().GasConsumed(), "should consume gas") + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneConsumeTxSizeGasDecorator(sdkauthante.NewConsumeGasForTxSizeDecorator(s.App().AccountKeeper())), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + cachedCtx.GasMeter().RefundGas(cachedCtx.GasMeter().GasConsumed(), "reset") + s.Require().Zero(cachedCtx.GasMeter().GasConsumed(), "gas meter should be reset before running test spec for decorator") + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/07_deduct_fee_it_test.go b/app/antedl/duallane/07_deduct_fee_it_test.go new file mode 100644 index 0000000000..7c005c9d76 --- /dev/null +++ b/app/antedl/duallane/07_deduct_fee_it_test.go @@ -0,0 +1,325 @@ +package duallane_test + +import ( + "math/big" + + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/constants" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + "github.com/ethereum/go-ethereum/common/math" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLDeductFeeDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + balance := func(ctx sdk.Context, accAddr sdk.AccAddress) sdkmath.Int { + return s.App().BankKeeper().GetBalance(ctx, accAddr, constants.BaseDenom).Amount + } + + originalBalanceAcc1 := balance(s.Ctx(), acc1.GetCosmosAddress()) + originalBalanceAcc2 := balance(s.Ctx(), acc2.GetCosmosAddress()) + + nodeConfigMinGasPrices := sdk.NewDecCoins(sdk.NewDecCoin(constants.BaseDenom, baseFee.AddRaw(1e9))) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + onSuccess func(ctx sdk.Context, tx sdk.Tx) + }{ + { + name: "pass - single-ETH - legacy tx, should deduct exact tx fee", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + gasPrices := ethTx.GasPrice() + + fee := sdkmath.NewIntFromBigInt(gasPrices).MulRaw(21000) + wantLaterBalance := originalBalanceAcc1.Sub(fee) + s.Equal(wantLaterBalance.String(), balance(ctx, acc1.GetCosmosAddress()).String(), "should deduct tx fee") + s.Equal(originalBalanceAcc2.String(), balance(ctx, acc2.GetCosmosAddress()).String(), "should not affect receiver account") + }, + }, + { + name: "pass - single-ETH - legacy tx, should set correct priority = gas prices", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + gasPrices := ethTx.GasPrice() + s.Equal(gasPrices.Int64(), ctx.Priority()) + }, + }, + { + name: "fail - single-ETH - legacy tx, should reject if gas price is lower than base fee", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + decoratorSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + }, + { + name: "fail - single-ETH - check-tx, should reject if gas price is lower than node config min-gas-prices", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.AddRaw(1).BigInt(), // greater than base fee but lower than node config + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts(). + WithCheckTx(). + WithNodeMinGasPrices(nodeConfigMinGasPrices). + WantsErrMsgContains("gas prices lower than node config"), + decoratorSpec: ts(). + WithCheckTx(). + WithNodeMinGasPrices(nodeConfigMinGasPrices). + WantsErrMsgContains("gas prices lower than node config"), + }, + { + name: "pass - single-ETH - dynamic fee tx, should deduct tx fee", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + effectiveGasPrices := math.BigMin(new(big.Int).Add(ethTx.GasTipCap(), baseFee.BigInt()), ethTx.GasFeeCap()) + effectiveFee := new(big.Int).Mul(effectiveGasPrices, big.NewInt(21000)) + wantLaterBalance := originalBalanceAcc1.Sub(sdkmath.NewIntFromBigInt(effectiveFee)) + s.Equal(wantLaterBalance.String(), balance(ctx, acc1.GetCosmosAddress()).String(), "should deduct tx fee") + s.Equal(originalBalanceAcc2.String(), balance(ctx, acc2.GetCosmosAddress()).String(), "should not affect receiver account") + }, + }, + { + name: "pass - single-ETH - dynamic fee tx, should set priority = effective gas prices", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + effectiveGasPrices := math.BigMin(new(big.Int).Add(ethTx.GasTipCap(), baseFee.BigInt()), ethTx.GasFeeCap()) + s.Equal(effectiveGasPrices.Int64(), ctx.Priority()) + }, + }, + { + name: "fail - single-ETH - dynamic fee tx, should reject if effective gas price is lower than base fee", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + decoratorSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + }, + { + name: "pass - single-Cosmos - without Dynamic Fee ext, should deduct exact tx fee", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + wantLaterBalance := originalBalanceAcc1.Sub(sdkmath.NewInt(1e18)) + s.Equal(wantLaterBalance.String(), balance(ctx, acc1.GetCosmosAddress()).String(), "should deduct tx fee") + s.Equal(originalBalanceAcc2.String(), balance(ctx, acc2.GetCosmosAddress()).String(), "should not affect receiver account") + }, + }, + { + name: "pass - single-Cosmos - without Dynamic Fee ext, should set priority = gas prices", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + gasPrices := sdkmath.NewInt(1e18).QuoRaw(500_000) + s.Equal(gasPrices.Int64(), ctx.Priority()) + }, + }, + { + name: "fail - single-Cosmos - without Dynamic Fee ext, should reject if gas prices is lower than base fee", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(1e18).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + decoratorSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + }, + { + name: "pass - single-Cosmos - with Dynamic Fee ext, should deduct exact tx fee", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.WithExtOptDynamicFeeTx() + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + gasPrices := sdkmath.NewInt(1e18).QuoRaw(500_000) + s.Require().True(baseFee.LT(gasPrices)) + effectiveGasPrices := baseFee + effectiveFee := effectiveGasPrices.MulRaw(500_000) + + s.NotEqual(effectiveFee.String(), sdkmath.NewInt(1e18), "effective fee should not be the original fee") + + wantLaterBalance := originalBalanceAcc1.Sub(effectiveFee) + s.Equal(wantLaterBalance.String(), balance(ctx, acc1.GetCosmosAddress()).String(), "should deduct tx fee") + s.Equal(originalBalanceAcc2.String(), balance(ctx, acc2.GetCosmosAddress()).String(), "should not affect receiver account") + }, + }, + { + name: "pass - single-Cosmos - with Dynamic Fee ext, set priority = effective gas prices", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.WithExtOptDynamicFeeTx() + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + gasPrices := sdkmath.NewInt(1e18).QuoRaw(500_000) + s.Require().True(baseFee.LT(gasPrices)) + effectiveGasPrices := baseFee + s.Equal(effectiveGasPrices.Int64(), ctx.Priority()) + }, + }, + { + name: "fail - single-Cosmos - with Dynamic Fee ext, should reject if effective gas prices is lower than base fee", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(1e18).BigFeeAmount(1) + tb.WithExtOptDynamicFeeTx() + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + decoratorSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), + }, + { + name: "fail - single-Cosmos - check-tx, should reject if gas price is lower than node config min-gas-prices", + tx: func(ctx sdk.Context) sdk.Tx { + const gasLimit = 500_000 + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1). + SetGasLimit(gasLimit). + SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, baseFee.AddRaw(1).MulRaw(gasLimit)))) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts(). + WithCheckTx(). + WithNodeMinGasPrices(nodeConfigMinGasPrices). + WantsErrMsgContains("gas prices lower than node config"), + decoratorSpec: ts(). + WithCheckTx(). + WithNodeMinGasPrices(nodeConfigMinGasPrices). + WantsErrMsgContains("gas prices lower than node config"), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneDeductFeeDecorator(sdkauthante.NewDeductFeeDecorator(s.App().AccountKeeper(), s.App().BankKeeper(), s.App().FeeGrantKeeper(), s.ATS.HandlerOptions.TxFeeChecker)), + ) + + if tt.onSuccess != nil { + tt.anteSpec.OnSuccess(tt.onSuccess) + tt.decoratorSpec.OnSuccess(tt.onSuccess) + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/08_set_pubkey_it_test.go b/app/antedl/duallane/08_set_pubkey_it_test.go new file mode 100644 index 0000000000..e254e31407 --- /dev/null +++ b/app/antedl/duallane/08_set_pubkey_it_test.go @@ -0,0 +1,100 @@ +package duallane_test + +import ( + "math/big" + + "github.com/EscanBE/evermint/v12/constants" + "github.com/EscanBE/evermint/v12/integration_test_util" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLSetPubKeyDecorator() { + acc1 := integration_test_util.NewTestAccount(s.T(), nil) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + s.ATS.CITS.MintCoin(acc1, sdk.NewInt64Coin(constants.BaseDenom, 3e18)) + + baseFee := s.BaseFee(s.Ctx()) + + { + account := s.App().AccountKeeper().GetAccount(s.Ctx(), acc1.GetCosmosAddress()) + s.Require().True( + account == nil || account.GetPubKey() == nil, + "account should not have pubkey set", + ) + } + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - will not do anything, pubkey remaining unset", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + account := s.App().AccountKeeper().GetAccount(ctx, acc1.GetCosmosAddress()) + s.Require().NotNil(account, "account should be exists") + s.Nil(account.GetPubKey(), "pubkey should not be set") + }), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + account := s.App().AccountKeeper().GetAccount(ctx, acc1.GetCosmosAddress()) + s.Require().True( + account == nil || account.GetPubKey() == nil, + "account should not have pubkey set", + ) + }), + }, + { + name: "pass - single-Cosmos - pubkey should be set", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + account := s.App().AccountKeeper().GetAccount(ctx, acc1.GetCosmosAddress()) + s.Require().NotNil(account, "account should be exists") + s.NotNil(account.GetPubKey(), "pubkey should be set") + }), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + account := s.App().AccountKeeper().GetAccount(ctx, acc1.GetCosmosAddress()) + s.Require().NotNil(account, "account should be exists") + s.NotNil(account.GetPubKey(), "pubkey should be set") + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneSetPubKeyDecorator(sdkauthante.NewSetPubKeyDecorator(s.App().AccountKeeper())), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/09_validate_sig_count_it_test.go b/app/antedl/duallane/09_validate_sig_count_it_test.go new file mode 100644 index 0000000000..58da65e599 --- /dev/null +++ b/app/antedl/duallane/09_validate_sig_count_it_test.go @@ -0,0 +1,80 @@ +package duallane_test + +import ( + "math/big" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLValidateSigCountDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + const sigCountLimit = 2 + { + // set sig count limit + accountParams, err := s.App().AccountKeeper().Params.Get(s.Ctx()) + s.Require().NoError(err) + accountParams.TxSigLimit = sigCountLimit + err = s.App().AccountKeeper().Params.Set(s.Ctx(), accountParams) + s.Require().NoError(err) + } + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - will not do anything", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-Cosmos - should consume gas", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + // TODO: add test-case multisig + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneValidateSigCountDecorator(sdkauthante.NewValidateSigCountDecorator(s.App().AccountKeeper())), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/10_sig_gas_consume_it_test.go b/app/antedl/duallane/10_sig_gas_consume_it_test.go new file mode 100644 index 0000000000..64ffe5d7e8 --- /dev/null +++ b/app/antedl/duallane/10_sig_gas_consume_it_test.go @@ -0,0 +1,76 @@ +package duallane_test + +import ( + "math/big" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLSigGasConsumeDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - should not consume gas", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Zero(ctx.GasMeter().GasConsumed(), "no gas should be consumed") + }), + }, + { + name: "pass - single-Cosmos - should consume gas", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + // consume more than just sig gas because the ante also need to fetch params info + s.Less(21000, int(ctx.GasMeter().GasConsumed()), "gas should be consumed") + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneSigGasConsumeDecorator(sdkauthante.NewSigGasConsumeDecorator(s.App().AccountKeeper(), s.ATS.HandlerOptions.SigGasConsumer)), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + cachedCtx.GasMeter().RefundGas(cachedCtx.GasMeter().GasConsumed(), "reset") + s.Require().Zero(cachedCtx.GasMeter().GasConsumed(), "gas meter should be reset before running test spec for decorator") + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/11_sig_verification_it_test.go b/app/antedl/duallane/11_sig_verification_it_test.go new file mode 100644 index 0000000000..dba1cb54ee --- /dev/null +++ b/app/antedl/duallane/11_sig_verification_it_test.go @@ -0,0 +1,103 @@ +package duallane_test + +import ( + "math/big" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLSigVerificationDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - should verify signature", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Zero(ctx.GasMeter().GasConsumed(), "no gas should be consumed") + }), + }, + { + name: "fail - single-ETH - reject if signature mismatch", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg.From = acc2.GetCosmosAddress().String() // signer != sender + + return s.TxB().SetMsgs(ethMsg).AutoGasLimit().AutoFee().Tx() + }, + anteSpec: ts().WantsErrMsgContains("mis-match sender address"), + decoratorSpec: ts().WantsErrMsgContains("mis-match sender address"), + }, + { + name: "pass - single-Cosmos - verify signature", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - reject if signature mis-match", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc2 /* signer != sender */, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("pubKey does not match signer address"), + decoratorSpec: ts().WantsErrMsgContains("signature verification failed; please verify account number"), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneSigVerificationDecorator( + *s.App().AccountKeeper(), + *s.App().EvmKeeper(), + sdkauthante.NewSigVerificationDecorator(s.App().AccountKeeper(), s.ATS.HandlerOptions.SignModeHandler), + ), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/12_increment_sequence_it_test.go b/app/antedl/duallane/12_increment_sequence_it_test.go new file mode 100644 index 0000000000..fa76dcb475 --- /dev/null +++ b/app/antedl/duallane/12_increment_sequence_it_test.go @@ -0,0 +1,111 @@ +package duallane_test + +import ( + "math/big" + + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (s *DLTestSuite) Test_DLIncrementSequenceDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + onSuccess func(ctx sdk.Context, tx sdk.Tx) + }{ + { + name: "pass - single-ETH - should increase nonce", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + seq, err := s.App().AccountKeeper().GetSequence(ctx, acc1.GetCosmosAddress()) + s.Require().NoError(err) + s.Equal(1, int(seq), "sequence should be increased") + }, + }, + { + name: "pass - single-ETH - should set flag nonce increased", + tx: func(ctx sdk.Context) sdk.Tx { + s.App().EvmKeeper().SetFlagSenderNonceIncreasedByAnteHandle(ctx, false) // ensure reset flag + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + s.True(s.App().EvmKeeper().IsSenderNonceIncreasedByAnteHandle(ctx), "this flag should be set by this decorator") + }, + }, + { + name: "pass - single-Cosmos - should increase sequence", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + seq, err := s.App().AccountKeeper().GetSequence(ctx, acc1.GetCosmosAddress()) + s.Require().NoError(err) + s.Equal(1, int(seq), "sequence should be increased") + }, + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + duallane.NewDualLaneIncrementSequenceDecorator( + *s.App().AccountKeeper(), + *s.App().EvmKeeper(), + sdkauthante.NewIncrementSequenceDecorator(s.App().AccountKeeper()), + ), + ) + + if tt.onSuccess != nil { + tt.anteSpec.OnSuccess(tt.onSuccess) + tt.decoratorSpec.OnSuccess(tt.onSuccess) + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/duallane/fee_checker.go b/app/antedl/duallane/fee_checker.go index cc4b06b753..470c27efbe 100644 --- a/app/antedl/duallane/fee_checker.go +++ b/app/antedl/duallane/fee_checker.go @@ -20,6 +20,8 @@ import ( evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" ) +// TODO ES: adjust priority = gas fee cap instead of effective gas price + // DualLaneFeeChecker returns CosmosTxDynamicFeeChecker or EthereumTxDynamicFeeChecker based on the transaction content. func DualLaneFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { diff --git a/app/antedl/duallane/setup_it_test.go b/app/antedl/duallane/setup_it_test.go new file mode 100644 index 0000000000..782c593488 --- /dev/null +++ b/app/antedl/duallane/setup_it_test.go @@ -0,0 +1,90 @@ +package duallane_test + +import ( + "testing" + + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + integration_test_util "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" +) + +var ts = itutiltypes.NewAnteTestSpec + +type DLTestSuite struct { + suite.Suite + ATS *integration_test_util.AnteIntegrationTestSuite +} + +func (s *DLTestSuite) App() itutiltypes.ChainApp { + return s.ATS.CITS.ChainApp +} + +func (s *DLTestSuite) Ctx() sdk.Context { + return s.ATS.CITS.CurrentContext +} + +func (s *DLTestSuite) TxB() *itutiltypes.TxBuilder { + return s.ATS.CITS.TxBuilder() +} + +func (s *DLTestSuite) SignCosmosTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + return s.ATS.CITS.SignCosmosTx(ctx, account, txBuilder) +} + +func (s *DLTestSuite) SignEthereumTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txData ethtypes.TxData, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + return s.ATS.CITS.SignEthereumTx(ctx, account, txData, txBuilder) +} + +func (s *DLTestSuite) PureSignEthereumTx( + account *itutiltypes.TestAccount, + txData ethtypes.TxData, +) *evmtypes.MsgEthereumTx { + ethMsg, err := s.ATS.CITS.PureSignEthereumTx(account, txData) + s.Require().NoError(err) + return ethMsg +} + +func (s *DLTestSuite) BaseFee( + ctx sdk.Context, +) sdkmath.Int { + return s.App().FeeMarketKeeper().GetBaseFee(ctx) +} + +func TestDLTestSuite(t *testing.T) { + suite.Run(t, new(DLTestSuite)) +} + +func (s *DLTestSuite) SetupSuite() { +} + +func (s *DLTestSuite) SetupTest() { + cs := integration_test_util.CreateChainIntegrationTestSuiteFromChainConfig( + s.T(), s.Require(), + integration_test_util.IntegrationTestChain1, + true, + ) + s.ATS = integration_test_util.CreateAnteIntegrationTestSuite(cs) +} + +func (s *DLTestSuite) TearDownTest() { + s.ATS.CITS.Cleanup() +} + +func (s *DLTestSuite) TearDownSuite() { +} diff --git a/app/antedl/evmlane/993e_exec_without_error.go b/app/antedl/evmlane/993e_exec_without_error.go index 2d84f46397..19134f4dce 100644 --- a/app/antedl/evmlane/993e_exec_without_error.go +++ b/app/antedl/evmlane/993e_exec_without_error.go @@ -36,8 +36,8 @@ func NewEvmLaneExecWithoutErrorDecorator(ak authkeeper.AccountKeeper, ek evmkeep // AnteHandle emits some basic events for the eth messages func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - if ctx.IsCheckTx() { - // allow, re-check-tx is included + if ctx.IsCheckTx() || ctx.IsReCheckTx() { + // allow } else if simulate { // allow } else { @@ -58,40 +58,38 @@ func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim panic(err) // should be checked by basic validation } - { - // create a branched context for simulation - simulationCtx, _ := ctx.CacheContext() + // create a branched context for simulation + simulationCtx, _ := ctx.CacheContext() - { // rollback the nonce which was increased by previous ante handle - acc := ed.ak.GetAccount(simulationCtx, ethMsg.GetFrom()) - err := acc.SetSequence(acc.GetSequence() - 1) - if err != nil { - panic(err) - } - ed.ak.SetAccount(simulationCtx, acc) - ed.ek.SetFlagSenderNonceIncreasedByAnteHandle(simulationCtx, false) + { // rollback the nonce which was increased by previous ante handle + acc := ed.ak.GetAccount(simulationCtx, ethMsg.GetFrom()) + err := acc.SetSequence(acc.GetSequence() - 1) + if err != nil { + panic(err) } + ed.ak.SetAccount(simulationCtx, acc) + ed.ek.SetFlagSenderNonceIncreasedByAnteHandle(simulationCtx, false) + } - var evm *vm.EVM - { // initialize EVM - txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(simulationCtx.HeaderHash())) - txConfig = txConfig.WithTxTypeFromMessage(ethCoreMsg) - stateDB := statedb.New(simulationCtx, &ed.ek, txConfig) - evmParams := ed.ek.GetParams(simulationCtx) - evmCfg := &statedb.EVMConfig{ - Params: evmParams, - ChainConfig: evmParams.ChainConfig.EthereumConfig(ed.ek.ChainID()), - CoinBase: common.Address{}, - BaseFee: baseFee.BigInt(), - NoBaseFee: false, - } - evm = ed.ek.NewEVM(simulationCtx, ethCoreMsg, evmCfg, evmtypes.NewNoOpTracer(), stateDB) - } - gasPool := core.GasPool(ethCoreMsg.Gas()) - _, err := evmkeeper.ApplyMessage(evm, ethCoreMsg, &gasPool) - if err != nil { - return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, err), "tx simulation execution failed") + var evm *vm.EVM + { // initialize EVM + txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(simulationCtx.HeaderHash())) + txConfig = txConfig.WithTxTypeFromMessage(ethCoreMsg) + stateDB := statedb.New(simulationCtx, &ed.ek, txConfig) + evmParams := ed.ek.GetParams(simulationCtx) + evmCfg := &statedb.EVMConfig{ + Params: evmParams, + ChainConfig: evmParams.ChainConfig.EthereumConfig(ed.ek.ChainID()), + CoinBase: common.Address{}, + BaseFee: baseFee.BigInt(), + NoBaseFee: false, } + evm = ed.ek.NewEVM(simulationCtx, ethCoreMsg, evmCfg, evmtypes.NewNoOpTracer(), stateDB) + } + gasPool := core.GasPool(ethCoreMsg.Gas()) + _, err = evmkeeper.ApplyMessage(evm, ethCoreMsg, &gasPool) + if err != nil { + return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, err), "tx simulation execution failed") } return next(ctx, tx, simulate) diff --git a/app/antedl/setup_it_test.go b/app/antedl/setup_it_test.go deleted file mode 100644 index 0894f1bc75..0000000000 --- a/app/antedl/setup_it_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package antedl_test - -import ( - "testing" - - "github.com/stretchr/testify/suite" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/EscanBE/evermint/v12/integration_test_util" - itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" -) - -type AnteTestSuite struct { - suite.Suite - CITS *integration_test_util.ChainIntegrationTestSuite -} - -func (s *AnteTestSuite) App() itutiltypes.ChainApp { - return s.CITS.ChainApp -} - -func (s *AnteTestSuite) Ctx() sdk.Context { - return s.CITS.CurrentContext -} - -func (s *AnteTestSuite) Commit() { - s.CITS.Commit() -} - -func TestAnteTestSuite(t *testing.T) { - suite.Run(t, new(AnteTestSuite)) -} - -func (s *AnteTestSuite) SetupSuite() { -} - -func (s *AnteTestSuite) SetupTest() { - s.CITS = integration_test_util.CreateChainIntegrationTestSuite(s.T(), s.Require()) -} - -func (s *AnteTestSuite) TearDownTest() { - s.CITS.Cleanup() -} - -func (s *AnteTestSuite) TearDownSuite() { -} - -func (s *AnteTestSuite) ca() itutiltypes.ChainApp { - return s.CITS.ChainApp -} diff --git a/app/antedl/utils/tx.go b/app/antedl/utils/tx.go index 35e14c4985..3224dc467d 100644 --- a/app/antedl/utils/tx.go +++ b/app/antedl/utils/tx.go @@ -36,6 +36,10 @@ func IsEthereumTx(tx sdk.Tx) bool { return true // allow no extension } + if nonCriticalOps := extTx.GetNonCriticalExtensionOptions(); len(nonCriticalOps) != 0 { + return false + } + opts := extTx.GetExtensionOptions() if len(opts) == 0 { return true diff --git a/integration_test_util/ante_suite.go b/integration_test_util/ante_suite.go new file mode 100644 index 0000000000..5a8e5e823d --- /dev/null +++ b/integration_test_util/ante_suite.go @@ -0,0 +1,111 @@ +package integration_test_util + +import ( + "github.com/EscanBE/evermint/v12/app/antedl" + "github.com/EscanBE/evermint/v12/app/antedl/duallane" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" +) + +func CreateAnteIntegrationTestSuite(chain *ChainIntegrationTestSuite) *AnteIntegrationTestSuite { + distrKeeper := chain.ChainApp.DistributionKeeper() + options := antedl.HandlerOptions{ + Cdc: chain.EncodingConfig.Codec, + AccountKeeper: chain.ChainApp.AccountKeeper(), + BankKeeper: chain.ChainApp.BankKeeper(), + DistributionKeeper: &distrKeeper, + StakingKeeper: chain.ChainApp.StakingKeeper(), + FeegrantKeeper: chain.ChainApp.FeeGrantKeeper(), + IBCKeeper: chain.ChainApp.IbcKeeper(), + FeeMarketKeeper: chain.ChainApp.FeeMarketKeeper(), + EvmKeeper: chain.ChainApp.EvmKeeper(), + VAuthKeeper: chain.ChainApp.VAuthKeeper(), + ExtensionOptionChecker: duallane.OnlyAllowExtensionOptionDynamicFeeTxForCosmosTxs, + SignModeHandler: chain.EncodingConfig.TxConfig.SignModeHandler(), + SigGasConsumer: duallane.SigVerificationGasConsumer, + TxFeeChecker: duallane.DualLaneFeeChecker(chain.ChainApp.EvmKeeper(), chain.ChainApp.FeeMarketKeeper()), + }.WithDefaultDisabledNestedMsgs() + + return &AnteIntegrationTestSuite{ + CITS: chain, + HandlerOptions: options, + } +} + +type AnteIntegrationTestSuite struct { + CITS *ChainIntegrationTestSuite + HandlerOptions antedl.HandlerOptions +} + +func (s *AnteIntegrationTestSuite) Require() *require.Assertions { + return s.CITS.Require() +} + +// RunTestSpec will attempt to run the test spec on a branched context. +func (s *AnteIntegrationTestSuite) RunTestSpec(ctx sdk.Context, tx sdk.Tx, ts *itutiltypes.AnteTestSpec, anteDecorator bool) { + if ts == nil { + s.CITS.T().Skipf("skipping test-case because test-spec is not provided, anteDecorator = %t", anteDecorator) + return + } + + cachedCtx, _ := ctx.CacheContext() + if anteDecorator { + s.Require().NotNil(ts.Ante, "this mode target one specific decorator so required") + } else { + s.Require().Nil(ts.Ante, "this mode is going through all decorators") + ts.Ante = antedl.NewAnteHandler(s.HandlerOptions) + } + + if ts.NodeMinGasPrices != nil { + cachedCtx = cachedCtx.WithMinGasPrices(*ts.NodeMinGasPrices) + } + + if ts.ReCheckTx { + cachedCtx = cachedCtx.WithIsCheckTx(true).WithIsReCheckTx(true) + } else { + cachedCtx = cachedCtx.WithIsCheckTx(ts.CheckTx) + } + + newCtx, err := ts.Ante(cachedCtx, tx, ts.Simulate) + + defer func() { + if ts.PostRunRegardlessStatus != nil { + ts.PostRunRegardlessStatus(newCtx, err, tx) + } + }() + + defer func() { + if ts.WantPriority != nil { + s.Require().Equal(*ts.WantPriority, newCtx.Priority(), "mis-match priority") + } + }() + + if ts.WantErr { + s.Require().Error(err) + + defer func() { + if ts.PostRunOnFail != nil { + ts.PostRunOnFail(newCtx, err, tx) + } + }() + + if ts.WantErrMsgContains != nil { + wantErrMsgContains := *ts.WantErrMsgContains + s.Require().NotEmpty(wantErrMsgContains, "bad setup test-case") + s.Require().ErrorContains(err, wantErrMsgContains) + } else { + s.CITS.Require().FailNow("require setup check error message") + } + + return + } + + s.Require().NoError(err) + defer func() { + if ts.PostRunOnSuccess != nil { + ts.PostRunOnSuccess(newCtx, tx) + } + }() +} diff --git a/integration_test_util/chain_suite_tx.go b/integration_test_util/chain_suite_tx.go index b209fb3638..bf54abb200 100644 --- a/integration_test_util/chain_suite_tx.go +++ b/integration_test_util/chain_suite_tx.go @@ -6,6 +6,7 @@ import ( "encoding/hex" evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" + ethtypes "github.com/ethereum/go-ethereum/core/types" errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" @@ -121,11 +122,15 @@ func (suite *ChainIntegrationTestSuite) PrepareCosmosTx( txBuilder.SetFeeGranter(args.FeeGranter) - return suite.signCosmosTx( + err := suite.signCosmosTx( ctx, account, txBuilder, ) + if err != nil { + return nil, err + } + return txBuilder.GetTx(), nil } // signCosmosTx signs the cosmos transaction on the txBuilder provided using @@ -134,19 +139,19 @@ func (suite *ChainIntegrationTestSuite) signCosmosTx( ctx sdk.Context, account *itutiltypes.TestAccount, txBuilder client.TxBuilder, -) (authsigning.Tx, error) { +) error { suite.Require().NotNil(account) txCfg := suite.EncodingConfig.TxConfig signMode, err := authsigning.APISignModeToInternal(txCfg.SignModeHandler().DefaultMode()) if err != nil { - return nil, err + return err } seq, err := suite.ChainApp.AccountKeeper().GetSequence(ctx, account.GetCosmosAddress()) if err != nil { - return nil, err + return err } // First round: we gather all the signer infos. We use the "set empty @@ -163,7 +168,7 @@ func (suite *ChainIntegrationTestSuite) signCosmosTx( sigsV2 := []signing.SignatureV2{sigV2} if err := txBuilder.SetSignatures(sigsV2...); err != nil { - return nil, err + return err } // Second round: all signer infos are set, so each signer can sign. @@ -181,14 +186,11 @@ func (suite *ChainIntegrationTestSuite) signCosmosTx( seq, ) if err != nil { - return nil, err + return err } sigsV2 = []signing.SignatureV2{sigV2} - if err = txBuilder.SetSignatures(sigsV2...); err != nil { - return nil, err - } - return txBuilder.GetTx(), nil + return txBuilder.SetSignatures(sigsV2...) } // QueryTxResponse returns the TxResponse for the given tx @@ -205,3 +207,86 @@ func (suite *ChainIntegrationTestSuite) QueryTxResponse(tx authsigning.Tx) *sdkt suite.Require().NotNil(txResponse) return txResponse } + +// TxBuilder returns a custom tx builder +func (suite *ChainIntegrationTestSuite) TxBuilder() *itutiltypes.TxBuilder { + return itutiltypes.NewTxBuilder(suite.EncodingConfig, suite.Require) +} + +// SignCosmosTx inserts signature, gas, fee to the tx builder +func (suite *ChainIntegrationTestSuite) SignCosmosTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + tb := txBuilder.ClientTxBuilder() + err := suite.signCosmosTx(ctx, account, tb) + return tb, err +} + +// SignEthereumMsg inserts signature, gas, fee to the tx builder +func (suite *ChainIntegrationTestSuite) SignEthereumMsg( + _ sdk.Context, + account *itutiltypes.TestAccount, + ethMsg *evmtypes.MsgEthereumTx, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + if err := suite.PureSignEthereumMsg(account, ethMsg); err != nil { + return nil, err + } + + ethTx := ethMsg.AsTransaction() + txFee := sdk.NewCoins(sdk.NewCoin( + suite.ChainConstantsConfig.GetMinDenom(), + sdkmath.NewIntFromBigInt(evmutils.EthTxFee(ethTx)), + )) + + tb := txBuilder.ClientTxBuilder() + if err := tb.SetMsgs(ethMsg); err != nil { + return nil, err + } + + tb.SetGasLimit(ethTx.Gas()) + tb.SetFeeAmount(txFee) + return tb, nil +} + +func (suite *ChainIntegrationTestSuite) PureSignEthereumMsg( + account *itutiltypes.TestAccount, + ethMsg *evmtypes.MsgEthereumTx, +) error { + return ethMsg.Sign(suite.EthSigner, itutiltypes.NewSigner(account.PrivateKey)) +} + +// SignEthereumTx inserts signature, gas, fee to the tx builder +func (suite *ChainIntegrationTestSuite) SignEthereumTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txData ethtypes.TxData, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + ethMsg, err := suite.PureSignEthereumTx(account, txData) + if err != nil { + return nil, err + } + + return suite.SignEthereumMsg(ctx, account, ethMsg, txBuilder) +} + +func (suite *ChainIntegrationTestSuite) PureSignEthereumTx( + account *itutiltypes.TestAccount, + txData ethtypes.TxData, +) (*evmtypes.MsgEthereumTx, error) { + ethTx := ethtypes.NewTx(txData) + ethMsg := &evmtypes.MsgEthereumTx{} + + if err := ethMsg.FromEthereumTx(ethTx, account.GetEthAddress()); err != nil { + return nil, err + } + + if err := suite.PureSignEthereumMsg(account, ethMsg); err != nil { + return nil, err + } + + return ethMsg, nil +} diff --git a/integration_test_util/types/accounts.go b/integration_test_util/types/accounts.go index ce4aa008bb..f460d50866 100644 --- a/integration_test_util/types/accounts.go +++ b/integration_test_util/types/accounts.go @@ -78,6 +78,11 @@ func (a TestAccount) GetEthAddress() common.Address { return common.BytesToAddress(a.GetPubKey().Address().Bytes()) } +func (a TestAccount) GetEthAddressP() *common.Address { + ethAddr := a.GetEthAddress() + return ðAddr +} + func (a TestAccount) ComputeContractAddress(nonce uint64) common.Address { return crypto.CreateAddress(a.GetEthAddress(), nonce) } diff --git a/integration_test_util/types/ante_test_spec.go b/integration_test_util/types/ante_test_spec.go new file mode 100644 index 0000000000..93e726234b --- /dev/null +++ b/integration_test_util/types/ante_test_spec.go @@ -0,0 +1,129 @@ +package types + +import sdk "github.com/cosmos/cosmos-sdk/types" + +type AnteTestSpec struct { + Ante sdk.AnteHandler + Simulate bool + NodeMinGasPrices *sdk.DecCoins + CheckTx bool + ReCheckTx bool + WantPriority *int64 + WantErr bool + WantErrMsgContains *string + PostRunOnSuccess func(ctx sdk.Context, tx sdk.Tx) // will be executed only when ante ran success + PostRunOnFail func(ctx sdk.Context, anteErr error, tx sdk.Tx) // will be executed only when ante ran failed + PostRunRegardlessStatus func(ctx sdk.Context, anteErr error, tx sdk.Tx) // will be executed regardless ante ran success or not +} + +func NewAnteTestSpec() *AnteTestSpec { + return &AnteTestSpec{} +} + +func (ts *AnteTestSpec) WithDecorator(d sdk.AnteDecorator) *AnteTestSpec { + if ts == nil { + return nil + } + ts.Ante = func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + return d.AnteHandle(ctx, tx, simulate, func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + return ctx, nil + }) + } + return ts +} + +func (ts *AnteTestSpec) WithSimulateOn() *AnteTestSpec { + if ts == nil { + return nil + } + ts.Simulate = true + return ts +} + +func (ts *AnteTestSpec) WithNodeMinGasPrices(minGasPrices sdk.DecCoins) *AnteTestSpec { + if ts == nil { + return nil + } + ts.NodeMinGasPrices = &minGasPrices + return ts +} + +func (ts *AnteTestSpec) WithCheckTx() *AnteTestSpec { + if ts == nil { + return nil + } + ts.CheckTx = true + return ts +} + +func (ts *AnteTestSpec) WithReCheckTx() *AnteTestSpec { + if ts == nil { + return nil + } + ts.CheckTx = true + ts.ReCheckTx = true + return ts +} + +func (ts *AnteTestSpec) WantsPriority(wantPriority int64) *AnteTestSpec { + if ts == nil { + return nil + } + ts.WantPriority = &wantPriority + return ts +} + +func (ts *AnteTestSpec) WantsSuccess() *AnteTestSpec { + if ts == nil { + return nil + } + if ts.WantErr { + panic("WantsErr called before") + } + return ts +} + +func (ts *AnteTestSpec) WantsErr() *AnteTestSpec { + if ts == nil { + return nil + } + ts.WantErr = true + return ts +} + +func (ts *AnteTestSpec) WantsErrMsgContains(msg string) *AnteTestSpec { + if ts == nil { + return nil + } + ts.WantErr = true + ts.WantErrMsgContains = &msg + return ts +} + +func (ts *AnteTestSpec) WantsErrMultiEthTx() *AnteTestSpec { + return ts.WantsErrMsgContains("MsgEthereumTx is not allowed to combine with other messages") +} + +func (ts *AnteTestSpec) OnSuccess(f func(ctx sdk.Context, tx sdk.Tx)) *AnteTestSpec { + if ts == nil { + return nil + } + ts.PostRunOnSuccess = f + return ts +} + +func (ts *AnteTestSpec) OnFail(f func(ctx sdk.Context, anteErr error, tx sdk.Tx)) *AnteTestSpec { + if ts == nil { + return nil + } + ts.PostRunOnFail = f + return ts +} + +func (ts *AnteTestSpec) PostRun(f func(ctx sdk.Context, anteErr error, tx sdk.Tx)) *AnteTestSpec { + if ts == nil { + return nil + } + ts.PostRunRegardlessStatus = f + return ts +} diff --git a/integration_test_util/types/tx_builder.go b/integration_test_util/types/tx_builder.go new file mode 100644 index 0000000000..5c7a6160fe --- /dev/null +++ b/integration_test_util/types/tx_builder.go @@ -0,0 +1,160 @@ +package types + +import ( + sdkmath "cosmossdk.io/math" + "github.com/EscanBE/evermint/v12/app/params" + "github.com/EscanBE/evermint/v12/constants" + evertypes "github.com/EscanBE/evermint/v12/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" +) + +type TxBuilder struct { + r func() *require.Assertions + + txBuilder client.TxBuilder + + msgs []sdk.Msg + gasLimit uint64 + fee sdk.Coins +} + +func NewTxBuilder(encodingConfig params.EncodingConfig, r func() *require.Assertions) *TxBuilder { + return &TxBuilder{ + r: r, + txBuilder: encodingConfig.TxConfig.NewTxBuilder(), + } +} + +func (tb *TxBuilder) SetMsgs(msgs ...sdk.Msg) *TxBuilder { + err := tb.txBuilder.SetMsgs(msgs...) + tb.r().NoError(err) + tb.msgs = msgs + return tb +} + +func (tb *TxBuilder) SetBankSendMsg(from, to *TestAccount, amount int64) *TxBuilder { + return tb.SetMultiBankSendMsg(from, to, amount, 1) +} + +func (tb *TxBuilder) SetMultiBankSendMsg(from, to *TestAccount, amount int64, count int) *TxBuilder { + msgs := make([]sdk.Msg, count) + for i := 0; i < count; i++ { + msgs[i] = &banktypes.MsgSend{ + FromAddress: from.GetCosmosAddress().String(), + ToAddress: to.GetCosmosAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(amount))), + } + } + return tb.SetMsgs(msgs...) +} + +func (tb *TxBuilder) SetGasLimit(gasLimit uint64) *TxBuilder { + tb.txBuilder.SetGasLimit(gasLimit) + return tb +} + +func (tb *TxBuilder) BigGasLimit() *TxBuilder { + return tb.SetGasLimit(30_000_000) +} + +func (tb *TxBuilder) SetMemo(memo string) *TxBuilder { + tb.txBuilder.SetMemo(memo) + return tb +} + +func (tb *TxBuilder) SetTimeoutHeight(height uint64) *TxBuilder { + tb.txBuilder.SetTimeoutHeight(height) + return tb +} + +func (tb *TxBuilder) SetFeeAmount(fee sdk.Coins) *TxBuilder { + tb.txBuilder.SetFeeAmount(fee) + return tb +} + +func (tb *TxBuilder) BigFeeAmount(amountDisplay int64) *TxBuilder { + return tb.SetFeeAmount(sdk.NewCoins( + sdk.NewCoin( + constants.BaseDenom, + sdkmath.NewInt(amountDisplay).Mul(sdkmath.NewInt(1e18)), + ), + )) +} + +func (tb *TxBuilder) SetFeeGranter(feeGranter sdk.AccAddress) *TxBuilder { + tb.txBuilder.SetFeeGranter(feeGranter) + return tb +} + +func (tb *TxBuilder) SetSignatures(signatures ...signingtypes.SignatureV2) *TxBuilder { + err := tb.txBuilder.SetSignatures(signatures...) + tb.r().NoError(err) + return tb +} + +func (tb *TxBuilder) SetExtensionOptions(extOpts ...proto.Message) *TxBuilder { + options := make([]*codectypes.Any, len(extOpts)) + + for i, opt := range extOpts { + option, err := codectypes.NewAnyWithValue(opt) + tb.r().NoError(err) + options[i] = option + } + + if txBuilder, ok := tb.txBuilder.(authtx.ExtensionOptionsTxBuilder); ok { + txBuilder.SetExtensionOptions(options...) + } + return tb +} + +func (tb *TxBuilder) WithExtOptEthTx() *TxBuilder { + return tb.SetExtensionOptions(&evmtypes.ExtensionOptionsEthereumTx{}) +} + +func (tb *TxBuilder) WithExtOptDynamicFeeTx() *TxBuilder { + return tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{}) +} + +func (tb *TxBuilder) AutoGasLimit() *TxBuilder { + var gas uint64 + for _, msg := range tb.msgs { + switch msg := msg.(type) { + case *evmtypes.MsgEthereumTx: + gas += msg.GetGas() + default: + tb.r().Failf("unsupported get gas for message", "type %T", msg) + } + } + tb.txBuilder.SetGasLimit(gas) + return tb +} + +func (tb *TxBuilder) AutoFee() *TxBuilder { + fees := sdk.Coins{} + for _, msg := range tb.msgs { + switch msg := msg.(type) { + case *evmtypes.MsgEthereumTx: + fees = fees.Add(sdk.NewCoin(constants.BaseDenom, sdkmath.NewIntFromBigInt(msg.GetFee()))) + default: + tb.r().Failf("unsupported get fee for message", "type %T", msg) + } + } + tb.txBuilder.SetFeeAmount(fees) + return tb +} + +func (tb *TxBuilder) ClientTxBuilder() client.TxBuilder { + return tb.txBuilder +} + +func (tb *TxBuilder) Tx() sdk.Tx { + return tb.txBuilder.GetTx() +} From 8d0f9c0c753e5c3967f2875b6f33eb6c353b8063 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 16 Sep 2024 22:21:13 +0700 Subject: [PATCH 05/14] refactor merge files --- app/antedl/duallane/07_deduct_fee.go | 223 +++++++++++++++++ ..._checker_test.go => 07_deduct_fee_test.go} | 0 app/antedl/duallane/fee_checker.go | 230 ------------------ 3 files changed, 223 insertions(+), 230 deletions(-) rename app/antedl/duallane/{fee_checker_test.go => 07_deduct_fee_test.go} (100%) delete mode 100644 app/antedl/duallane/fee_checker.go diff --git a/app/antedl/duallane/07_deduct_fee.go b/app/antedl/duallane/07_deduct_fee.go index c76c918a86..b4ed15b08a 100644 --- a/app/antedl/duallane/07_deduct_fee.go +++ b/app/antedl/duallane/07_deduct_fee.go @@ -1,8 +1,22 @@ package duallane import ( + "math" + "math/big" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" + + cmath "github.com/ethereum/go-ethereum/common/math" + + dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" + evertypes "github.com/EscanBE/evermint/v12/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" ) type DLDeductFeeDecorator struct { @@ -24,3 +38,212 @@ func NewDualLaneDeductFeeDecorator( func (dfd DLDeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { return dfd.cd.AnteHandle(ctx, tx, simulate, next) } + +// TODO ES: adjust priority = gas fee cap instead of effective gas price + +// DualLaneFeeChecker returns CosmosTxDynamicFeeChecker or EthereumTxDynamicFeeChecker based on the transaction content. +func DualLaneFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { + return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + var fc sdkauthante.TxFeeChecker + if dlanteutils.HasSingleEthereumMessage(tx) { + fc = EthereumTxDynamicFeeChecker(ek, fk) + } else { + fc = CosmosTxDynamicFeeChecker(ek, fk) + } + return fc(ctx, tx) + } +} + +// CosmosTxDynamicFeeChecker is implements `TxFeeChecker` +// that applies a dynamic fee to Cosmos txs follow EIP-1559. +func CosmosTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { + return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + if dlanteutils.HasSingleEthereumMessage(tx) { + panic("wrong call") + } + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if ctx.BlockHeight() == 0 { + // genesis transactions: fallback to min-gas-price logic + return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx) + } + + evmParams := ek.GetParams(ctx) + allowedFeeDenom := evmParams.EvmDenom + + feeMarketParams := fk.GetParams(ctx) + baseFee := feeMarketParams.BaseFee + + fees := feeTx.GetFee() + if err := validateSingleFee(fees, allowedFeeDenom); err != nil { + return nil, 0, err + } + fee := fees[0] + + var gasTipCap *sdkmath.Int + if hasExtOptsTx, ok := feeTx.(sdkauthante.HasExtensionOptionsTx); ok { + for _, opt := range hasExtOptsTx.GetExtensionOptions() { + if extOpt, ok := opt.GetCachedValue().(*evertypes.ExtensionOptionDynamicFeeTx); ok { + gasTipCap = &extOpt.MaxPriorityPrice + break + } + } + } + + var effectiveFee sdk.Coins + gas := feeTx.GetGas() + if gasTipCap != nil { // has Dynamic Fee Tx ext + // priority fee cannot be negative + if gasTipCap.IsNegative() { + return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas tip cap cannot be negative") + } + + gasFeeCap := fee.Amount.Quo(sdkmath.NewIntFromUint64(gas)) + + // Compute follow formula of Ethereum EIP-1559 + effectiveGasPrice := cmath.BigMin(new(big.Int).Add(gasTipCap.BigInt(), baseFee.BigInt()), gasFeeCap.BigInt()) + + // Dynamic Fee effective fee = effective gas price * gas + effectiveFee = sdk.NewCoins( + sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(effectiveGasPrice).Mul(sdkmath.NewIntFromUint64(gas))), + ) + } else { + // normal logic + effectiveFee = fees + } + + minGasPricesAllowed, minGasPricesSrc := getMinGasPricesAllowed(ctx, feeMarketParams, allowedFeeDenom) + priority, err := getTxPriority(effectiveFee, int64(gas), minGasPricesAllowed, minGasPricesSrc) + if err != nil { + return nil, 0, err + } + return effectiveFee, priority, nil + } +} + +// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per +// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price. +func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coins, int64, error) { + feeCoins := tx.GetFee() + minGasPrices := ctx.MinGasPrices() + gas := int64(tx.GetGas()) + + // Ensure that the provided fees meet a minimum threshold for the validator, + // if this is a CheckTx. This is only for local mempool purposes, and thus + // is only ran on check tx. + if ctx.IsCheckTx() && !minGasPrices.IsZero() { + requiredFees := make(sdk.Coins, len(minGasPrices)) + + // Determine the required fees by multiplying each required minimum gas + // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). + glDec := sdkmath.LegacyNewDec(gas) + for i, gp := range minGasPrices { + fee := gp.Amount.Mul(glDec) + requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) + } + + if !feeCoins.IsAnyGTE(requiredFees) { + return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + } + } + + priority, err := getTxPriority(feeCoins, gas, sdkmath.ZeroInt(), "") + if err != nil { + return nil, 0, err + } + return feeCoins, priority, nil +} + +// EthereumTxDynamicFeeChecker is implements `TxFeeChecker`. +func EthereumTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { + return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { + if !dlanteutils.HasSingleEthereumMessage(tx) || ctx.BlockHeight() == 0 { + panic("wrong call") + } + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + evmParams := ek.GetParams(ctx) + allowedFeeDenom := evmParams.EvmDenom + + feeMarketParams := fk.GetParams(ctx) + baseFee := feeMarketParams.BaseFee + + if err := validateSingleFee(feeTx.GetFee(), allowedFeeDenom); err != nil { + return nil, 0, err + } + + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + effectiveFee := sdk.NewCoins( + sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxEffectiveFee(ethTx, baseFee))), + ) + + minGasPricesAllowed, minGasPricesSrc := getMinGasPricesAllowed(ctx, feeMarketParams, allowedFeeDenom) + priority, err := getTxPriority(effectiveFee, int64(ethTx.Gas()), minGasPricesAllowed, minGasPricesSrc) + if err != nil { + return nil, 0, err + } + return effectiveFee, priority, nil + } +} + +// validateSingleFee validates if provided fee is only one type of coin, +// and denom must be exact match provided. +func validateSingleFee(fees sdk.Coins, allowedFeeDenom string) error { + if len(fees) != 1 { + return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "only one fee coin is allowed, got: %d", len(fees)) + } + fee := fees[0] + if fee.Denom != allowedFeeDenom { + return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "only '%s' is allowed as fee, got: %s", allowedFeeDenom, fee) + } + return nil +} + +// getMinGasPricesAllowed returns the biggest number among base fee and min-gas-prices of x/feemarket keeper. +// If the execution mode is check-tx (mempool), the validator min-gas-prices also included in the consideration. +func getMinGasPricesAllowed(ctx sdk.Context, fp feemarkettypes.Params, allowedFeeDenom string) (minGasPricesAllowed sdkmath.Int, minGasPricesSrc string) { + minGasPricesAllowed = fp.BaseFee + minGasPricesSrc = "base fee" + + if ctx.IsCheckTx() { // mempool + if !ctx.IsReCheckTx() { // no need to do it twice + validatorMinGasPrices := ctx.MinGasPrices().AmountOf(allowedFeeDenom).TruncateInt() + if minGasPricesAllowed.LT(validatorMinGasPrices) { + minGasPricesAllowed = validatorMinGasPrices + minGasPricesSrc = "node config" + } + } + } + + globalMinGasPrices := fp.MinGasPrice.TruncateInt() + if minGasPricesAllowed.LT(globalMinGasPrices) { + minGasPricesAllowed = globalMinGasPrices + minGasPricesSrc = "minimum global fee" + } + return +} + +// getTxPriority returns a naive tx priority based on the gas price. +// Gas price = fee / gas +func getTxPriority(fees sdk.Coins, gas int64, minGasPricesAllowed sdkmath.Int, minGasPricesSrc string) (priority int64, err error) { + fee := fees[0] // there is one and only one, validated before + priority = int64(math.MaxInt64) + + gasPrices := fee.Amount.QuoRaw(gas) + if gasPrices.LT(minGasPricesAllowed) { + err = errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas prices lower than %s, got: %s required: %s. Please retry using a higher gas price or a higher fee", minGasPricesSrc, gasPrices, minGasPricesAllowed) + return + } + + if gasPrices.IsInt64() { + priority = gasPrices.Int64() + } + + return +} diff --git a/app/antedl/duallane/fee_checker_test.go b/app/antedl/duallane/07_deduct_fee_test.go similarity index 100% rename from app/antedl/duallane/fee_checker_test.go rename to app/antedl/duallane/07_deduct_fee_test.go diff --git a/app/antedl/duallane/fee_checker.go b/app/antedl/duallane/fee_checker.go deleted file mode 100644 index 470c27efbe..0000000000 --- a/app/antedl/duallane/fee_checker.go +++ /dev/null @@ -1,230 +0,0 @@ -package duallane - -import ( - "math" - "math/big" - - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - - cmath "github.com/ethereum/go-ethereum/common/math" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - - dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" - evertypes "github.com/EscanBE/evermint/v12/types" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" -) - -// TODO ES: adjust priority = gas fee cap instead of effective gas price - -// DualLaneFeeChecker returns CosmosTxDynamicFeeChecker or EthereumTxDynamicFeeChecker based on the transaction content. -func DualLaneFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { - return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { - var fc authante.TxFeeChecker - if dlanteutils.HasSingleEthereumMessage(tx) { - fc = EthereumTxDynamicFeeChecker(ek, fk) - } else { - fc = CosmosTxDynamicFeeChecker(ek, fk) - } - return fc(ctx, tx) - } -} - -// CosmosTxDynamicFeeChecker is implements `TxFeeChecker` -// that applies a dynamic fee to Cosmos txs follow EIP-1559. -func CosmosTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { - return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { - if dlanteutils.HasSingleEthereumMessage(tx) { - panic("wrong call") - } - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") - } - - if ctx.BlockHeight() == 0 { - // genesis transactions: fallback to min-gas-price logic - return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx) - } - - evmParams := ek.GetParams(ctx) - allowedFeeDenom := evmParams.EvmDenom - - feeMarketParams := fk.GetParams(ctx) - baseFee := feeMarketParams.BaseFee - - fees := feeTx.GetFee() - if err := validateSingleFee(fees, allowedFeeDenom); err != nil { - return nil, 0, err - } - fee := fees[0] - - var gasTipCap *sdkmath.Int - if hasExtOptsTx, ok := feeTx.(authante.HasExtensionOptionsTx); ok { - for _, opt := range hasExtOptsTx.GetExtensionOptions() { - if extOpt, ok := opt.GetCachedValue().(*evertypes.ExtensionOptionDynamicFeeTx); ok { - gasTipCap = &extOpt.MaxPriorityPrice - break - } - } - } - - var effectiveFee sdk.Coins - gas := feeTx.GetGas() - if gasTipCap != nil { // has Dynamic Fee Tx ext - // priority fee cannot be negative - if gasTipCap.IsNegative() { - return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas tip cap cannot be negative") - } - - gasFeeCap := fee.Amount.Quo(sdkmath.NewIntFromUint64(gas)) - - // Compute follow formula of Ethereum EIP-1559 - effectiveGasPrice := cmath.BigMin(new(big.Int).Add(gasTipCap.BigInt(), baseFee.BigInt()), gasFeeCap.BigInt()) - - // Dynamic Fee effective fee = effective gas price * gas - effectiveFee = sdk.NewCoins( - sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(effectiveGasPrice).Mul(sdkmath.NewIntFromUint64(gas))), - ) - } else { - // normal logic - effectiveFee = fees - } - - minGasPricesAllowed, minGasPricesSrc := getMinGasPricesAllowed(ctx, feeMarketParams, allowedFeeDenom) - priority, err := getTxPriority(effectiveFee, int64(gas), minGasPricesAllowed, minGasPricesSrc) - if err != nil { - return nil, 0, err - } - return effectiveFee, priority, nil - } -} - -// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per -// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price. -func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coins, int64, error) { - feeCoins := tx.GetFee() - minGasPrices := ctx.MinGasPrices() - gas := int64(tx.GetGas()) - - // Ensure that the provided fees meet a minimum threshold for the validator, - // if this is a CheckTx. This is only for local mempool purposes, and thus - // is only ran on check tx. - if ctx.IsCheckTx() && !minGasPrices.IsZero() { - requiredFees := make(sdk.Coins, len(minGasPrices)) - - // Determine the required fees by multiplying each required minimum gas - // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). - glDec := sdkmath.LegacyNewDec(gas) - for i, gp := range minGasPrices { - fee := gp.Amount.Mul(glDec) - requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) - } - - if !feeCoins.IsAnyGTE(requiredFees) { - return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) - } - } - - priority, err := getTxPriority(feeCoins, gas, sdkmath.ZeroInt(), "") - if err != nil { - return nil, 0, err - } - return feeCoins, priority, nil -} - -// EthereumTxDynamicFeeChecker is implements `TxFeeChecker`. -func EthereumTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) authante.TxFeeChecker { - return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { - if !dlanteutils.HasSingleEthereumMessage(tx) || ctx.BlockHeight() == 0 { - panic("wrong call") - } - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") - } - - evmParams := ek.GetParams(ctx) - allowedFeeDenom := evmParams.EvmDenom - - feeMarketParams := fk.GetParams(ctx) - baseFee := feeMarketParams.BaseFee - - if err := validateSingleFee(feeTx.GetFee(), allowedFeeDenom); err != nil { - return nil, 0, err - } - - ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() - effectiveFee := sdk.NewCoins( - sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxEffectiveFee(ethTx, baseFee))), - ) - - minGasPricesAllowed, minGasPricesSrc := getMinGasPricesAllowed(ctx, feeMarketParams, allowedFeeDenom) - priority, err := getTxPriority(effectiveFee, int64(ethTx.Gas()), minGasPricesAllowed, minGasPricesSrc) - if err != nil { - return nil, 0, err - } - return effectiveFee, priority, nil - } -} - -// validateSingleFee validates if provided fee is only one type of coin, -// and denom must be exact match provided. -func validateSingleFee(fees sdk.Coins, allowedFeeDenom string) error { - if len(fees) != 1 { - return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "only one fee coin is allowed, got: %d", len(fees)) - } - fee := fees[0] - if fee.Denom != allowedFeeDenom { - return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "only '%s' is allowed as fee, got: %s", allowedFeeDenom, fee) - } - return nil -} - -// getMinGasPricesAllowed returns the biggest number among base fee and min-gas-prices of x/feemarket keeper. -// If the execution mode is check-tx (mempool), the validator min-gas-prices also included in the consideration. -func getMinGasPricesAllowed(ctx sdk.Context, fp feemarkettypes.Params, allowedFeeDenom string) (minGasPricesAllowed sdkmath.Int, minGasPricesSrc string) { - minGasPricesAllowed = fp.BaseFee - minGasPricesSrc = "base fee" - - if ctx.IsCheckTx() { // mempool - if !ctx.IsReCheckTx() { // no need to do it twice - validatorMinGasPrices := ctx.MinGasPrices().AmountOf(allowedFeeDenom).TruncateInt() - if minGasPricesAllowed.LT(validatorMinGasPrices) { - minGasPricesAllowed = validatorMinGasPrices - minGasPricesSrc = "node config" - } - } - } - - globalMinGasPrices := fp.MinGasPrice.TruncateInt() - if minGasPricesAllowed.LT(globalMinGasPrices) { - minGasPricesAllowed = globalMinGasPrices - minGasPricesSrc = "minimum global fee" - } - return -} - -// getTxPriority returns a naive tx priority based on the gas price. -// Gas price = fee / gas -func getTxPriority(fees sdk.Coins, gas int64, minGasPricesAllowed sdkmath.Int, minGasPricesSrc string) (priority int64, err error) { - fee := fees[0] // there is one and only one, validated before - priority = int64(math.MaxInt64) - - gasPrices := fee.Amount.QuoRaw(gas) - if gasPrices.LT(minGasPricesAllowed) { - err = errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas prices lower than %s, got: %s required: %s. Please retry using a higher gas price or a higher fee", minGasPricesSrc, gasPrices, minGasPricesAllowed) - return - } - - if gasPrices.IsInt64() { - priority = gasPrices.Int64() - } - - return -} From 4d758b182886c7f08a10c76f011dd1eb8d0f9d9b Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Mon, 16 Sep 2024 22:43:29 +0700 Subject: [PATCH 06/14] on dev Dual Lane AnteHandle IT --- .../991c_reject_eth_msgs_it_test.go | 126 +++++++++ .../992c_reject_authz_msgs_it_test.go | 240 ++++++++++++++++++ .../993c_vesting_msg_authorization_it_test.go | 185 ++++++++++++++ app/antedl/cosmoslane/setup_it_test.go | 90 +++++++ app/antedl/duallane/07_deduct_fee_it_test.go | 21 ++ .../evmlane/03e_validate_basic_eoa_it_test.go | 152 +++++++++++ app/antedl/evmlane/991e_setup_exec_it_test.go | 102 ++++++++ app/antedl/evmlane/992e_emit_event_it_test.go | 100 ++++++++ app/antedl/evmlane/setup_it_test.go | 90 +++++++ 9 files changed, 1106 insertions(+) create mode 100644 app/antedl/cosmoslane/991c_reject_eth_msgs_it_test.go create mode 100644 app/antedl/cosmoslane/992c_reject_authz_msgs_it_test.go create mode 100644 app/antedl/cosmoslane/993c_vesting_msg_authorization_it_test.go create mode 100644 app/antedl/cosmoslane/setup_it_test.go create mode 100644 app/antedl/evmlane/03e_validate_basic_eoa_it_test.go create mode 100644 app/antedl/evmlane/991e_setup_exec_it_test.go create mode 100644 app/antedl/evmlane/992e_emit_event_it_test.go create mode 100644 app/antedl/evmlane/setup_it_test.go diff --git a/app/antedl/cosmoslane/991c_reject_eth_msgs_it_test.go b/app/antedl/cosmoslane/991c_reject_eth_msgs_it_test.go new file mode 100644 index 0000000000..4e09c1995f --- /dev/null +++ b/app/antedl/cosmoslane/991c_reject_eth_msgs_it_test.go @@ -0,0 +1,126 @@ +package cosmoslane_test + +import ( + "math/big" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/EscanBE/evermint/v12/app/antedl/cosmoslane" + "github.com/EscanBE/evermint/v12/constants" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" +) + +func (s *CLTestSuite) Test_CLRejectEthereumMsgsDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - Multi-ETH - should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + ethMsg2 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 1, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + + return s.TxB().SetMsgs(ethMsg1, ethMsg2).AutoGasLimit().AutoFee().Tx() + }, + anteSpec: ts().WantsErrMultiEthTx(), + decoratorSpec: ts().WantsErrMsgContains("MsgEthereumTx cannot be mixed with Cosmos messages"), + }, + { + name: "pass - single-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - multi-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - Multi-ETH mixed Cosmos - should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + ethMsg1 := s.PureSignEthereumTx(acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }) + cosmosMsg2 := &banktypes.MsgSend{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: acc2.GetCosmosAddress().String(), + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt())), + } + + return s.TxB().SetMsgs(ethMsg1, cosmosMsg2).SetGasLimit(521_000).BigFeeAmount(1).Tx() + }, + anteSpec: ts().WantsErrMultiEthTx(), + decoratorSpec: ts().WantsErrMsgContains("MsgEthereumTx cannot be mixed with Cosmos messages"), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator(cosmoslane.NewCosmosLaneRejectEthereumMsgsDecorator()) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/cosmoslane/992c_reject_authz_msgs_it_test.go b/app/antedl/cosmoslane/992c_reject_authz_msgs_it_test.go new file mode 100644 index 0000000000..a595276793 --- /dev/null +++ b/app/antedl/cosmoslane/992c_reject_authz_msgs_it_test.go @@ -0,0 +1,240 @@ +package cosmoslane_test + +import ( + "math/big" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/gogoproto/proto" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/EscanBE/evermint/v12/app/antedl/cosmoslane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +func (s *CLTestSuite) Test_CLRejectAuthzMsgsDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + _30daysLater := s.Ctx().BlockTime().Add(30 * 24 * time.Hour) + + createSimpleGrantMsg := func(msg proto.Message) sdk.Msg { + msgGrant, err := authz.NewMsgGrant( + acc1.GetCosmosAddress(), + acc2.GetCosmosAddress(), + authz.NewGenericAuthorization(sdk.MsgTypeURL(msg)), + &_30daysLater, + ) + s.Require().NoError(err) + return msgGrant + } + + createSimpleGrantTx := func(ctx sdk.Context, msg proto.Message) sdk.Tx { + msgGrant := createSimpleGrantMsg(msg) + tb := s.TxB().SetMsgs(msgGrant).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + } + + createSimpleMsgExec := func(msg proto.Message) *authz.MsgExec { + msgExec := authz.NewMsgExec(acc1.GetCosmosAddress(), []sdk.Msg{msg}) + return &msgExec + } + + createSimpleMsgExecTx := func(ctx sdk.Context, msg proto.Message) sdk.Tx { + msgExec := createSimpleMsgExec(msg) + tb := s.TxB().SetMsgs(msgExec).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + } + + createMultiLevelMsgExecTx := func(ctx sdk.Context, msg proto.Message, levelCount int) sdk.Tx { + msgExec := createSimpleMsgExec(msg) + + for l := 1; l <= levelCount-1; l++ { + msgExec = createSimpleMsgExec(msgExec) + } + + tb := s.TxB().SetMsgs(msgExec).SetGasLimit(2_000_000).BigFeeAmount(2) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + } + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-Cosmos - MsgGrant which not violate policy should pass", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleGrantTx(ctx, &banktypes.MsgSend{}) + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - MsgGrant contains MsgEthereumTx should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleGrantTx(ctx, &evmtypes.MsgEthereumTx{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to grant"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to grant"), + }, + { + name: "fail - single-Cosmos - MsgGrant contains MsgCreateVestingAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleGrantTx(ctx, &vestingtypes.MsgCreateVestingAccount{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to grant"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to grant"), + }, + { + name: "fail - single-Cosmos - MsgGrant contains MsgCreatePeriodicVestingAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleGrantTx(ctx, &vestingtypes.MsgCreatePeriodicVestingAccount{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to grant"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to grant"), + }, + { + name: "fail - single-Cosmos - MsgGrant contains MsgCreatePermanentLockedAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleGrantTx(ctx, &vestingtypes.MsgCreatePermanentLockedAccount{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to grant"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to grant"), + }, + { + name: "pass - single-Cosmos - MsgExec which not violate policy should pass", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleMsgExecTx(ctx, &banktypes.MsgSend{}) + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - MsgExec contains MsgEthereumTx should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleMsgExecTx(ctx, &evmtypes.MsgEthereumTx{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - MsgExec contains MsgCreateVestingAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleMsgExecTx(ctx, &vestingtypes.MsgCreateVestingAccount{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - MsgExec contains MsgCreatePeriodicVestingAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleMsgExecTx(ctx, &vestingtypes.MsgCreatePeriodicVestingAccount{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - MsgExec contains MsgCreatePermanentLockedAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createSimpleMsgExecTx(ctx, &vestingtypes.MsgCreatePermanentLockedAccount{}) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "pass - single-Cosmos - multi-level-MsgExec which not violate policy should pass", + tx: func(ctx sdk.Context) sdk.Tx { + return createMultiLevelMsgExecTx(ctx, &banktypes.MsgSend{}, 2) + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - multi-level-MsgExec contains MsgEthereumTx should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createMultiLevelMsgExecTx(ctx, &evmtypes.MsgEthereumTx{}, 2) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - multi-level-MsgExec contains MsgCreateVestingAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createMultiLevelMsgExecTx(ctx, &vestingtypes.MsgCreateVestingAccount{}, 2) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - multi-level-MsgExec contains MsgCreatePeriodicVestingAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createMultiLevelMsgExecTx(ctx, &vestingtypes.MsgCreatePeriodicVestingAccount{}, 2) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - multi-level-MsgExec contains MsgCreatePermanentLockedAccount should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + return createMultiLevelMsgExecTx(ctx, &vestingtypes.MsgCreatePermanentLockedAccount{}, 2) + }, + anteSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + decoratorSpec: ts().WantsErrMsgContains("not allowed to be nested message"), + }, + { + name: "fail - single-Cosmos - multi-level-MsgExec will be rejected if nested level exceeds the limit", + tx: func(ctx sdk.Context) sdk.Tx { + return createMultiLevelMsgExecTx(ctx, &evmtypes.MsgEthereumTx{}, 3) + }, + anteSpec: ts().WantsErrMsgContains("nested level"), + decoratorSpec: ts().WantsErrMsgContains("nested level"), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + cosmoslane.NewCosmosLaneRejectAuthzMsgsDecorator(s.ATS.HandlerOptions.DisabledNestedMsgs), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/cosmoslane/993c_vesting_msg_authorization_it_test.go b/app/antedl/cosmoslane/993c_vesting_msg_authorization_it_test.go new file mode 100644 index 0000000000..26bb5a9351 --- /dev/null +++ b/app/antedl/cosmoslane/993c_vesting_msg_authorization_it_test.go @@ -0,0 +1,185 @@ +package cosmoslane_test + +import ( + "encoding/hex" + "math/big" + "time" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/EscanBE/evermint/v12/app/antedl/cosmoslane" + "github.com/EscanBE/evermint/v12/constants" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + "github.com/EscanBE/evermint/v12/rename_chain/marker" + vauthtypes "github.com/EscanBE/evermint/v12/x/vauth/types" +) + +func (s *CLTestSuite) Test_CLVestingMessagesAuthorizationDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + proof := vauthtypes.ProofExternalOwnedAccount{ + Account: marker.ReplaceAbleAddress("evm1xx2enpw8wzlr64xkdz2gh3c7epucfdftnqtcem"), + Hash: "0x" + hex.EncodeToString(crypto.Keccak256([]byte(vauthtypes.MessageToSign))), + Signature: "0xe665110439b1d18002ef866285f7e532090065ad74274560db5e8373d0cdb6297afefc70a5dd46c23e74bd3f0f262195f089b2923242a14e8e0791f4b0621a2c00", + } + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - should do nothing and pass", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-Cosmos - not vesting, should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-Cosmos - with EOA proof, should allow", + tx: func(ctx sdk.Context) sdk.Tx { + err := s.App().VAuthKeeper().SaveProofExternalOwnedAccount(ctx, proof) + s.Require().NoError(err) + + tb := s.TxB().SetMsgs(&vestingtypes.MsgCreateVestingAccount{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: proof.Account, + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18))), + EndTime: time.Now().Add(24 * time.Hour).Unix(), + Delayed: true, + }).SetGasLimit(500_000).BigFeeAmount(1) + + _, err = s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - without EOA proof, should reject", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMsgs(&vestingtypes.MsgCreateVestingAccount{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: proof.Account, + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18))), + EndTime: time.Now().Add(24 * time.Hour).Unix(), + Delayed: true, + }).SetGasLimit(500_000).BigFeeAmount(1) + + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("must prove account is external owned account"), + decoratorSpec: ts().WantsErrMsgContains("must prove account is external owned account"), + }, + { + name: "pass - multi-Cosmos - not vesting, should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - multi-Cosmos - with EOA proof, should allow", + tx: func(ctx sdk.Context) sdk.Tx { + err := s.App().VAuthKeeper().SaveProofExternalOwnedAccount(ctx, proof) + s.Require().NoError(err) + + tb := s.TxB().SetMsgs(&vestingtypes.MsgCreateVestingAccount{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: proof.Account, + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18))), + EndTime: time.Now().Add(24 * time.Hour).Unix(), + Delayed: true, + }, &vestingtypes.MsgCreateVestingAccount{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: proof.Account, + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18))), + EndTime: time.Now().Add(24 * time.Hour).Unix(), + Delayed: true, + }).SetGasLimit(500_000).BigFeeAmount(1) + + _, err = s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - multi-Cosmos - without EOA proof, should reject", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMsgs(&vestingtypes.MsgCreateVestingAccount{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: proof.Account, + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18))), + EndTime: time.Now().Add(24 * time.Hour).Unix(), + Delayed: true, + }, &vestingtypes.MsgCreateVestingAccount{ + FromAddress: acc1.GetCosmosAddress().String(), + ToAddress: proof.Account, + Amount: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18))), + EndTime: time.Now().Add(24 * time.Hour).Unix(), + Delayed: true, + }).SetGasLimit(500_000).BigFeeAmount(1) + + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("must prove account is external owned account"), + decoratorSpec: ts().WantsErrMsgContains("must prove account is external owned account"), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + cosmoslane.NewCosmosLaneVestingMessagesAuthorizationDecorator(*s.App().VAuthKeeper()), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/cosmoslane/setup_it_test.go b/app/antedl/cosmoslane/setup_it_test.go new file mode 100644 index 0000000000..189aee7ae8 --- /dev/null +++ b/app/antedl/cosmoslane/setup_it_test.go @@ -0,0 +1,90 @@ +package cosmoslane_test + +import ( + "testing" + + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + integration_test_util "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" +) + +var ts = itutiltypes.NewAnteTestSpec + +type CLTestSuite struct { + suite.Suite + ATS *integration_test_util.AnteIntegrationTestSuite +} + +func (s *CLTestSuite) App() itutiltypes.ChainApp { + return s.ATS.CITS.ChainApp +} + +func (s *CLTestSuite) Ctx() sdk.Context { + return s.ATS.CITS.CurrentContext +} + +func (s *CLTestSuite) TxB() *itutiltypes.TxBuilder { + return s.ATS.CITS.TxBuilder() +} + +func (s *CLTestSuite) SignCosmosTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + return s.ATS.CITS.SignCosmosTx(ctx, account, txBuilder) +} + +func (s *CLTestSuite) SignEthereumTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txData ethtypes.TxData, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + return s.ATS.CITS.SignEthereumTx(ctx, account, txData, txBuilder) +} + +func (s *CLTestSuite) PureSignEthereumTx( + account *itutiltypes.TestAccount, + txData ethtypes.TxData, +) *evmtypes.MsgEthereumTx { + ethMsg, err := s.ATS.CITS.PureSignEthereumTx(account, txData) + s.Require().NoError(err) + return ethMsg +} + +func (s *CLTestSuite) BaseFee( + ctx sdk.Context, +) sdkmath.Int { + return s.App().FeeMarketKeeper().GetBaseFee(ctx) +} + +func TestDLTestSuite(t *testing.T) { + suite.Run(t, new(CLTestSuite)) +} + +func (s *CLTestSuite) SetupSuite() { +} + +func (s *CLTestSuite) SetupTest() { + cs := integration_test_util.CreateChainIntegrationTestSuiteFromChainConfig( + s.T(), s.Require(), + integration_test_util.IntegrationTestChain1, + true, + ) + s.ATS = integration_test_util.CreateAnteIntegrationTestSuite(cs) +} + +func (s *CLTestSuite) TearDownTest() { + s.ATS.CITS.Cleanup() +} + +func (s *CLTestSuite) TearDownSuite() { +} diff --git a/app/antedl/duallane/07_deduct_fee_it_test.go b/app/antedl/duallane/07_deduct_fee_it_test.go index 7c005c9d76..4915a0442a 100644 --- a/app/antedl/duallane/07_deduct_fee_it_test.go +++ b/app/antedl/duallane/07_deduct_fee_it_test.go @@ -83,6 +83,27 @@ func (s *DLTestSuite) Test_DLDeductFeeDecorator() { s.Equal(gasPrices.Int64(), ctx.Priority()) }, }, + { + name: "pass - single-ETH - should pass even lower than node config min-gas-prices, when not check-tx", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WithNodeMinGasPrices(nodeConfigMinGasPrices).WantsSuccess(), + decoratorSpec: ts().WithNodeMinGasPrices(nodeConfigMinGasPrices).WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + gasPrices := ethTx.GasPrice() + s.Equal(gasPrices.Int64(), ctx.Priority()) + }, + }, { name: "fail - single-ETH - legacy tx, should reject if gas price is lower than base fee", tx: func(ctx sdk.Context) sdk.Tx { diff --git a/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go b/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go new file mode 100644 index 0000000000..b2395feba1 --- /dev/null +++ b/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go @@ -0,0 +1,152 @@ +package evmlane_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/EscanBE/evermint/v12/app/antedl/evmlane" + "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" +) + +func (s *ELTestSuite) Test_ELValidateBasicEoaDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + notExistsAccWithBalance := integration_test_util.NewTestAccount(s.T(), nil) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + onSuccess func(ctx sdk.Context, tx sdk.Tx) + }{ + { + name: "pass - single-ETH - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail Ante/pass Decorator - single-ETH - account not exists, should create account", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, notExistsAccWithBalance, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("insufficient funds"), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + s.Require().NotNil( + s.App().AccountKeeper().GetAccount(ctx, notExistsAccWithBalance.GetCosmosAddress()), + "account should be created", + ) + }, + }, + { + name: "fail - single-ETH - contract account should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + s.App().EvmKeeper().SetCodeHash(ctx, acc1.GetEthAddress(), common.BytesToHash([]byte{1, 2, 3})) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("the sender is not EOA"), + decoratorSpec: ts().WantsErrMsgContains("the sender is not EOA"), + }, + { + name: "fail - single-ETH - account not exists, but has code hash, should be rejected", + tx: func(ctx sdk.Context) sdk.Tx { + s.App().EvmKeeper().SetCodeHash(ctx, notExistsAccWithBalance.GetEthAddress(), common.BytesToHash([]byte{1, 2, 3})) + + ctb, err := s.SignEthereumTx(ctx, notExistsAccWithBalance, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("the sender is not EOA"), + decoratorSpec: ts().WantsErrMsgContains("the sender is not EOA"), + }, + { + name: "pass - single-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - multi-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + evmlane.NewEvmLaneValidateBasicEoaDecorator(*s.App().AccountKeeper(), *s.App().EvmKeeper()), + ) + + if tt.onSuccess != nil { + tt.anteSpec.OnSuccess(tt.onSuccess) + tt.decoratorSpec.OnSuccess(tt.onSuccess) + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/evmlane/991e_setup_exec_it_test.go b/app/antedl/evmlane/991e_setup_exec_it_test.go new file mode 100644 index 0000000000..b1e4d64cb2 --- /dev/null +++ b/app/antedl/evmlane/991e_setup_exec_it_test.go @@ -0,0 +1,102 @@ +package evmlane_test + +import ( + "math" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/EscanBE/evermint/v12/app/antedl/evmlane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" +) + +func (s *ELTestSuite) Test_ELSetupExecutionDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + onSuccess func(ctx sdk.Context, tx sdk.Tx) + }{ + { + name: "pass - single-ETH - should setup execution context", + tx: func(ctx sdk.Context) sdk.Tx { + s.Zero(s.App().EvmKeeper().GetRawTxCountTransient(ctx)) + s.Zero(s.App().EvmKeeper().GetGasUsedForTdxIndexTransient(ctx, 0)) + s.Empty(s.App().EvmKeeper().GetTxReceiptsTransient(ctx)) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + s.Equal(1, int(s.App().EvmKeeper().GetRawTxCountTransient(ctx)), "should be increased by 1") + + s.Equal(21000, int(s.App().EvmKeeper().GetGasUsedForTdxIndexTransient(ctx, 0)), "should be tx gas (assume tx was failed)") + + s.NotEmpty(s.App().EvmKeeper().GetTxReceiptsTransient(ctx), "receipt should be set") + + s.Equal(uint64(21_000), ctx.GasMeter().Limit(), "this decorator should use infinite gas meter with limit") + s.Equal(uint64(math.MaxUint64), ctx.GasMeter().GasRemaining(), "this decorator should use infinite gas meter with limit") + }, + }, + { + name: "pass - single-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - multi-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + evmlane.NewEvmLaneSetupExecutionDecorator(*s.App().EvmKeeper()), + ) + + if tt.onSuccess != nil { + tt.anteSpec.OnSuccess(tt.onSuccess) + tt.decoratorSpec.OnSuccess(tt.onSuccess) + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/evmlane/992e_emit_event_it_test.go b/app/antedl/evmlane/992e_emit_event_it_test.go new file mode 100644 index 0000000000..801cd826a8 --- /dev/null +++ b/app/antedl/evmlane/992e_emit_event_it_test.go @@ -0,0 +1,100 @@ +package evmlane_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/EscanBE/evermint/v12/app/antedl/evmlane" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" +) + +func (s *ELTestSuite) Test_ELEmitEventDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + tests := []struct { + name string + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - event should be emitted", + tx: func(ctx sdk.Context) sdk.Tx { + s.Zero(s.App().EvmKeeper().GetRawTxCountTransient(ctx)) + s.Zero(s.App().EvmKeeper().GetGasUsedForTdxIndexTransient(ctx, 0)) + s.Empty(s.App().EvmKeeper().GetTxReceiptsTransient(ctx)) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + events := ctx.EventManager().Events() + s.Require().NotEmpty(events) + s.Len(events, 1) + event := events[0] + s.Equal(evmtypes.EventTypeEthereumTx, event.Type) + if s.Len(event.Attributes, 2) { + s.Equal(evmtypes.AttributeKeyEthereumTxHash, event.Attributes[0].Key) + s.Equal(evmtypes.AttributeKeyTxIndex, event.Attributes[1].Key) + } + }), + }, + { + name: "pass - single-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Empty(ctx.EventManager().Events()) + }), + }, + { + name: "pass - multi-Cosmos - should pass", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetMultiBankSendMsg(acc1, acc2, 1, 3).SetGasLimit(1_500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Empty(ctx.EventManager().Events()) + }), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + evmlane.NewEvmLaneEmitEventDecorator(*s.App().EvmKeeper()), + ) + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/app/antedl/evmlane/setup_it_test.go b/app/antedl/evmlane/setup_it_test.go new file mode 100644 index 0000000000..0b419aa6d3 --- /dev/null +++ b/app/antedl/evmlane/setup_it_test.go @@ -0,0 +1,90 @@ +package evmlane_test + +import ( + "testing" + + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + integration_test_util "github.com/EscanBE/evermint/v12/integration_test_util" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" +) + +var ts = itutiltypes.NewAnteTestSpec + +type ELTestSuite struct { + suite.Suite + ATS *integration_test_util.AnteIntegrationTestSuite +} + +func (s *ELTestSuite) App() itutiltypes.ChainApp { + return s.ATS.CITS.ChainApp +} + +func (s *ELTestSuite) Ctx() sdk.Context { + return s.ATS.CITS.CurrentContext +} + +func (s *ELTestSuite) TxB() *itutiltypes.TxBuilder { + return s.ATS.CITS.TxBuilder() +} + +func (s *ELTestSuite) SignCosmosTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + return s.ATS.CITS.SignCosmosTx(ctx, account, txBuilder) +} + +func (s *ELTestSuite) SignEthereumTx( + ctx sdk.Context, + account *itutiltypes.TestAccount, + txData ethtypes.TxData, + txBuilder *itutiltypes.TxBuilder, +) (client.TxBuilder, error) { + return s.ATS.CITS.SignEthereumTx(ctx, account, txData, txBuilder) +} + +func (s *ELTestSuite) PureSignEthereumTx( + account *itutiltypes.TestAccount, + txData ethtypes.TxData, +) *evmtypes.MsgEthereumTx { + ethMsg, err := s.ATS.CITS.PureSignEthereumTx(account, txData) + s.Require().NoError(err) + return ethMsg +} + +func (s *ELTestSuite) BaseFee( + ctx sdk.Context, +) sdkmath.Int { + return s.App().FeeMarketKeeper().GetBaseFee(ctx) +} + +func TestDLTestSuite(t *testing.T) { + suite.Run(t, new(ELTestSuite)) +} + +func (s *ELTestSuite) SetupSuite() { +} + +func (s *ELTestSuite) SetupTest() { + cs := integration_test_util.CreateChainIntegrationTestSuiteFromChainConfig( + s.T(), s.Require(), + integration_test_util.IntegrationTestChain1, + true, + ) + s.ATS = integration_test_util.CreateAnteIntegrationTestSuite(cs) +} + +func (s *ELTestSuite) TearDownTest() { + s.ATS.CITS.Cleanup() +} + +func (s *ELTestSuite) TearDownSuite() { +} From fdab3128031d47c7bbb37f017b8e5bf14fbc0c81 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 00:43:54 +0700 Subject: [PATCH 07/14] improve check exec without err --- app/antedl/evmlane/993e_exec_without_error.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/antedl/evmlane/993e_exec_without_error.go b/app/antedl/evmlane/993e_exec_without_error.go index 19134f4dce..932bd0ee22 100644 --- a/app/antedl/evmlane/993e_exec_without_error.go +++ b/app/antedl/evmlane/993e_exec_without_error.go @@ -61,7 +61,8 @@ func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim // create a branched context for simulation simulationCtx, _ := ctx.CacheContext() - { // rollback the nonce which was increased by previous ante handle + if ed.ek.IsSenderNonceIncreasedByAnteHandle(simulationCtx) { + // rollback the nonce which was increased by previous ante handle acc := ed.ak.GetAccount(simulationCtx, ethMsg.GetFrom()) err := acc.SetSequence(acc.GetSequence() - 1) if err != nil { @@ -87,10 +88,13 @@ func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim evm = ed.ek.NewEVM(simulationCtx, ethCoreMsg, evmCfg, evmtypes.NewNoOpTracer(), stateDB) } gasPool := core.GasPool(ethCoreMsg.Gas()) - _, err = evmkeeper.ApplyMessage(evm, ethCoreMsg, &gasPool) + execResult, err := evmkeeper.ApplyMessage(evm, ethCoreMsg, &gasPool) if err != nil { return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, err), "tx simulation execution failed") } + if execResult.Err != nil { + return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, execResult.Err), "tx simulation execution failed with EVM error") + } return next(ctx, tx, simulate) } From ffb75d3abd8f8f9b3cfbc77ef744a57f46fc7dab Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 00:46:35 +0700 Subject: [PATCH 08/14] on dev Dual Lane AnteHandle IT --- .../duallane/11_sig_verification_it_test.go | 6 +- .../993e_exec_without_error_it_test.go | 174 ++++++++++++++++++ integration_test_util/ante_suite.go | 6 +- 3 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 app/antedl/evmlane/993e_exec_without_error_it_test.go diff --git a/app/antedl/duallane/11_sig_verification_it_test.go b/app/antedl/duallane/11_sig_verification_it_test.go index dba1cb54ee..7d5d4cbd04 100644 --- a/app/antedl/duallane/11_sig_verification_it_test.go +++ b/app/antedl/duallane/11_sig_verification_it_test.go @@ -36,10 +36,8 @@ func (s *DLTestSuite) Test_DLSigVerificationDecorator() { s.Require().NoError(err) return ctb.GetTx() }, - anteSpec: ts().WantsSuccess(), - decoratorSpec: ts().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { - s.Zero(ctx.GasMeter().GasConsumed(), "no gas should be consumed") - }), + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), }, { name: "fail - single-ETH - reject if signature mismatch", diff --git a/app/antedl/evmlane/993e_exec_without_error_it_test.go b/app/antedl/evmlane/993e_exec_without_error_it_test.go new file mode 100644 index 0000000000..ed1add1e6a --- /dev/null +++ b/app/antedl/evmlane/993e_exec_without_error_it_test.go @@ -0,0 +1,174 @@ +package evmlane_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/EscanBE/evermint/v12/app/antedl/evmlane" + "github.com/EscanBE/evermint/v12/constants" + itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" +) + +func (s *ELTestSuite) Test_ELExecWithoutErrorDecorator() { + acc1 := s.ATS.CITS.WalletAccounts.Number(1) + acc2 := s.ATS.CITS.WalletAccounts.Number(2) + + baseFee := s.BaseFee(s.Ctx()) + + acc1Balance := s.App().BankKeeper().GetBalance(s.Ctx(), acc1.GetCosmosAddress(), constants.BaseDenom).Amount + + tests := []struct { + name string + checkTx bool + reCheckTx bool + simulation bool + tx func(ctx sdk.Context) sdk.Tx + anteSpec *itutiltypes.AnteTestSpec + decoratorSpec *itutiltypes.AnteTestSpec + }{ + { + name: "pass - single-ETH - checkTx, should allow tx which can execute without error", + checkTx: true, + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "pass - single-ETH - non-check-tx, should ignore txs which can exec with error", + checkTx: false, + reCheckTx: false, + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: acc1Balance.AddRaw(1).BigInt(), // send more than have + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-ETH - check-tx, should reject txs which can exec with error", + checkTx: true, + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: acc1Balance.AddRaw(1).BigInt(), // send more than have + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("tx simulation execution failed"), + decoratorSpec: ts().WantsErrMsgContains("tx simulation execution failed"), + }, + { + name: "fail - single-ETH - re-check-tx, should reject txs which can exec with error", + reCheckTx: true, + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: acc1Balance.AddRaw(1).BigInt(), // send more than have + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("tx simulation execution failed"), + decoratorSpec: ts().WantsErrMsgContains("tx simulation execution failed"), + }, + { + name: "fail - single-ETH - simulation, should reject txs which can exec with error", + simulation: true, + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ + Nonce: 0, + GasFeeCap: baseFee.BigInt(), + GasTipCap: big.NewInt(1), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: acc1Balance.AddRaw(1).BigInt(), // send more than have + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("tx simulation execution failed"), + decoratorSpec: ts().WantsErrMsgContains("tx simulation execution failed"), + }, + { + name: "pass - single-Cosmos - checkTx, normal tx should pass", + checkTx: true, + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail Ante/pass Decorator - single-Cosmos - checkTx, invalid tx should be ignored by this Ante", + checkTx: true, + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc2 /*sender != signer*/, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("pubKey does not match signer address"), + decoratorSpec: ts().WantsSuccess(), + }, + } + for _, tt := range tests { + s.Run(tt.name, func() { + cachedCtx, _ := s.Ctx().CacheContext() + + tt.decoratorSpec.WithDecorator( + evmlane.NewEvmLaneExecWithoutErrorDecorator(*s.App().AccountKeeper(), *s.App().EvmKeeper()), + ) + + if tt.reCheckTx { + cachedCtx = cachedCtx.WithIsReCheckTx(true) + } else if tt.checkTx { + cachedCtx = cachedCtx.WithIsCheckTx(true) + } + + if tt.simulation { + tt.anteSpec = tt.anteSpec.WithSimulateOn() + tt.decoratorSpec = tt.decoratorSpec.WithSimulateOn() + } + + tx := tt.tx(cachedCtx) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) + + s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) + }) + } +} diff --git a/integration_test_util/ante_suite.go b/integration_test_util/ante_suite.go index 5a8e5e823d..5253077edb 100644 --- a/integration_test_util/ante_suite.go +++ b/integration_test_util/ante_suite.go @@ -64,8 +64,8 @@ func (s *AnteIntegrationTestSuite) RunTestSpec(ctx sdk.Context, tx sdk.Tx, ts *i if ts.ReCheckTx { cachedCtx = cachedCtx.WithIsCheckTx(true).WithIsReCheckTx(true) - } else { - cachedCtx = cachedCtx.WithIsCheckTx(ts.CheckTx) + } else if ts.CheckTx { + cachedCtx = cachedCtx.WithIsCheckTx(true) } newCtx, err := ts.Ante(cachedCtx, tx, ts.Simulate) @@ -96,7 +96,7 @@ func (s *AnteIntegrationTestSuite) RunTestSpec(ctx sdk.Context, tx sdk.Tx, ts *i s.Require().NotEmpty(wantErrMsgContains, "bad setup test-case") s.Require().ErrorContains(err, wantErrMsgContains) } else { - s.CITS.Require().FailNow("require setup check error message") + s.Require().FailNow("require setup check error message") } return From 3ee5306ea245e1a6af691b55994cd5f79628f9d1 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 01:15:01 +0700 Subject: [PATCH 09/14] remove legacy Ante --- app/ante/ante.go | 53 - app/ante/cosmos/authz.go | 87 - app/ante/cosmos/authz_test.go | 488 ------ app/ante/cosmos/fees.go | 211 --- app/ante/cosmos/fees_test.go | 267 ---- app/ante/cosmos/interfaces.go | 16 - app/ante/cosmos/min_price.go | 101 -- app/ante/cosmos/min_price_test.go | 191 --- app/ante/cosmos/reject_msgs.go | 27 - app/ante/cosmos/setup_test.go | 148 -- app/ante/cosmos/utils_test.go | 126 -- app/ante/cosmos/vesting.go | 49 - app/ante/cosmos/vesting_test.go | 105 -- app/ante/doc.go | 12 - app/ante/evm/ante_test.go | 1394 ----------------- app/ante/evm/eth.go | 349 ----- app/ante/evm/eth_test.go | 1115 ------------- app/ante/evm/fee_checker.go | 158 -- app/ante/evm/fee_checker_test.go | 301 ---- app/ante/evm/fees.go | 124 -- app/ante/evm/fees_test.go | 314 ---- app/ante/evm/interfaces.go | 46 - app/ante/evm/setup_ctx.go | 218 --- app/ante/evm/setup_ctx_test.go | 93 -- app/ante/evm/setup_test.go | 119 -- app/ante/evm/signverify_test.go | 130 -- app/ante/evm/sigs_test.go | 41 - app/ante/evm/sigverify.go | 68 - app/ante/evm/utils_test.go | 613 -------- app/ante/handler_options.go | 151 -- app/ante/handler_options_test.go | 229 --- app/ante/integration_test.go | 71 - app/ante/setup_test.go | 65 - app/ante/sigverify.go | 88 -- app/ante/sigverify_test.go | 114 -- app/ante/utils/fee_checker.go | 7 - app/ante/utils/interfaces.go | 25 - app/ante/utils/setup_test.go | 116 -- app/ante/utils_test.go | 38 - .../duallane/11_sig_verification_test.go | 3 +- app/app.go | 29 +- testutil/statedb.go | 13 - 42 files changed, 2 insertions(+), 7911 deletions(-) delete mode 100644 app/ante/ante.go delete mode 100644 app/ante/cosmos/authz.go delete mode 100644 app/ante/cosmos/authz_test.go delete mode 100644 app/ante/cosmos/fees.go delete mode 100644 app/ante/cosmos/fees_test.go delete mode 100644 app/ante/cosmos/interfaces.go delete mode 100644 app/ante/cosmos/min_price.go delete mode 100644 app/ante/cosmos/min_price_test.go delete mode 100644 app/ante/cosmos/reject_msgs.go delete mode 100644 app/ante/cosmos/setup_test.go delete mode 100644 app/ante/cosmos/utils_test.go delete mode 100644 app/ante/cosmos/vesting.go delete mode 100644 app/ante/cosmos/vesting_test.go delete mode 100644 app/ante/doc.go delete mode 100644 app/ante/evm/ante_test.go delete mode 100644 app/ante/evm/eth.go delete mode 100644 app/ante/evm/eth_test.go delete mode 100644 app/ante/evm/fee_checker.go delete mode 100644 app/ante/evm/fee_checker_test.go delete mode 100644 app/ante/evm/fees.go delete mode 100644 app/ante/evm/fees_test.go delete mode 100644 app/ante/evm/interfaces.go delete mode 100644 app/ante/evm/setup_ctx.go delete mode 100644 app/ante/evm/setup_ctx_test.go delete mode 100644 app/ante/evm/setup_test.go delete mode 100644 app/ante/evm/signverify_test.go delete mode 100644 app/ante/evm/sigs_test.go delete mode 100644 app/ante/evm/sigverify.go delete mode 100644 app/ante/evm/utils_test.go delete mode 100644 app/ante/handler_options.go delete mode 100644 app/ante/handler_options_test.go delete mode 100644 app/ante/integration_test.go delete mode 100644 app/ante/setup_test.go delete mode 100644 app/ante/sigverify.go delete mode 100644 app/ante/sigverify_test.go delete mode 100644 app/ante/utils/fee_checker.go delete mode 100644 app/ante/utils/interfaces.go delete mode 100644 app/ante/utils/setup_test.go delete mode 100644 app/ante/utils_test.go delete mode 100644 testutil/statedb.go diff --git a/app/ante/ante.go b/app/ante/ante.go deleted file mode 100644 index cae1b6400f..0000000000 --- a/app/ante/ante.go +++ /dev/null @@ -1,53 +0,0 @@ -package ante - -import ( - errorsmod "cosmossdk.io/errors" - "github.com/EscanBE/evermint/v12/constants" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" -) - -// NewAnteHandler returns an ante handler responsible for attempting to route an -// Ethereum or SDK transaction to an internal ante handler for performing -// transaction-level processing (e.g. fee payment, signature verification) before -// being passed onto it's respective handler. -func NewAnteHandler(options HandlerOptions) sdk.AnteHandler { - return func( - ctx sdk.Context, tx sdk.Tx, sim bool, - ) (newCtx sdk.Context, err error) { - var anteHandler sdk.AnteHandler - - txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx) - if ok { - opts := txWithExtensions.GetExtensionOptions() - if len(opts) > 0 { - switch typeURL := opts[0].GetTypeUrl(); typeURL { - case constants.EthermintExtensionOptionsEthereumTx: - // handle as *evmtypes.MsgEthereumTx - anteHandler = newEVMAnteHandler(options) - case constants.EthermintExtensionOptionDynamicFeeTx: - // cosmos-sdk tx with dynamic fee extension - anteHandler = newCosmosAnteHandler(options) - default: - return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownExtensionOptions, - "rejecting tx with unsupported extension option: %s", typeURL, - ) - } - - return anteHandler(ctx, tx, sim) - } - } - - // handle as totally normal Cosmos SDK tx - switch tx.(type) { - case sdk.Tx: - anteHandler = newCosmosAnteHandler(options) - default: - return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx) - } - - return anteHandler(ctx, tx, sim) - } -} diff --git a/app/ante/cosmos/authz.go b/app/ante/cosmos/authz.go deleted file mode 100644 index cc3d613e23..0000000000 --- a/app/ante/cosmos/authz.go +++ /dev/null @@ -1,87 +0,0 @@ -package cosmos - -import ( - "fmt" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/x/authz" -) - -// maxNestedMsgs defines a cap for the number of nested messages on a MsgExec message -const maxNestedMsgs = 7 - -// AuthzLimiterDecorator blocks certain msg types from being granted or executed -// within the authorization module. -type AuthzLimiterDecorator struct { - // disabledMsgTypes is the type urls of the msgs to block. - disabledMsgTypes map[string]bool -} - -// NewAuthzLimiterDecorator creates a decorator to block certain msg types from being granted or executed within authz. -func NewAuthzLimiterDecorator(disabledMsgTypes map[string]bool) AuthzLimiterDecorator { - return AuthzLimiterDecorator{ - disabledMsgTypes: disabledMsgTypes, - } -} - -func (ald AuthzLimiterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - if err := ald.checkDisabledMsgs(tx.GetMsgs(), false, 1); err != nil { - return ctx, errorsmod.Wrapf(errortypes.ErrUnauthorized, err.Error()) - } - return next(ctx, tx, simulate) -} - -// checkDisabledMsgs iterates through the msgs and returns an error if it finds any unauthorized msgs. -// -// When searchOnlyInAuthzMsgs is enabled, only authz MsgGrant and MsgExec are blocked, if they contain unauthorized msg types. -// Otherwise any msg matching the disabled types are blocked, regardless of being in an authz msg or not. -// -// This method is recursive as MsgExec's can wrap other MsgExecs. The check for nested messages is performed up to the -// maxNestedMsgs threshold. If there are more than that limit, it returns an error -func (ald AuthzLimiterDecorator) checkDisabledMsgs(msgs []sdk.Msg, isAuthzInnerMsg bool, nestedLvl int) error { - if nestedLvl >= maxNestedMsgs { - return fmt.Errorf("found more nested msgs than permited. Limit is : %d", maxNestedMsgs) - } - for _, msg := range msgs { - switch msg := msg.(type) { - case *authz.MsgExec: - innerMsgs, err := msg.GetMessages() - if err != nil { - return err - } - nestedLvl++ - if err := ald.checkDisabledMsgs(innerMsgs, true, nestedLvl); err != nil { - return err - } - case *authz.MsgGrant: - authorization, err := msg.GetAuthorization() - if err != nil { - return err - } - - url := authorization.MsgTypeURL() - if ald.isDisabledMsg(url) { - return fmt.Errorf("found disabled msg type: %s", url) - } - default: - url := sdk.MsgTypeURL(msg) - if isAuthzInnerMsg && ald.isDisabledMsg(url) { - return fmt.Errorf("found disabled msg type: %s", url) - } - } - } - return nil -} - -// isDisabledMsg returns true if the given message is in the list of restricted -// messages from the AnteHandler. -func (ald AuthzLimiterDecorator) isDisabledMsg(msgTypeURL string) bool { - if ald.disabledMsgTypes == nil { - return false - } - - isDisabled, isFound := ald.disabledMsgTypes[msgTypeURL] - return isFound && isDisabled -} diff --git a/app/ante/cosmos/authz_test.go b/app/ante/cosmos/authz_test.go deleted file mode 100644 index 3cb867812a..0000000000 --- a/app/ante/cosmos/authz_test.go +++ /dev/null @@ -1,488 +0,0 @@ -package cosmos_test - -import ( - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - - sdkmath "cosmossdk.io/math" - - "github.com/EscanBE/evermint/v12/app/ante" - "github.com/EscanBE/evermint/v12/constants" - ethtypes "github.com/ethereum/go-ethereum/core/types" - - abci "github.com/cometbft/cometbft/abci/types" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - "github.com/cosmos/cosmos-sdk/x/authz" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - cosmosante "github.com/EscanBE/evermint/v12/app/ante/cosmos" - "github.com/EscanBE/evermint/v12/testutil" - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" -) - -func TestAuthzLimiterDecorator(t *testing.T) { - testPrivKeys, testAddresses, err := generatePrivKeyAddressPairs(5) - require.NoError(t, err) - - distantFuture := time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC) - - validator := sdk.ValAddress(testAddresses[4]) - stakingAuthDelegate, err := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{validator}, nil, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, nil) - require.NoError(t, err) - - stakingAuthUndelegate, err := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{validator}, nil, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, nil) - require.NoError(t, err) - - disabledAuthzMsgs := ante.HandlerOptions{}.WithDefaultDisabledAuthzMsgs().DisabledAuthzMsgs - disabledAuthzMsgs[sdk.MsgTypeURL(&stakingtypes.MsgUndelegate{})] = true - decorator := cosmosante.NewAuthzLimiterDecorator(disabledAuthzMsgs) - - testCases := []struct { - name string - msgs []sdk.Msg - checkTx bool - expectedErr error - }{ - { - name: "pass - enabled msg - non blocked msg", - msgs: []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[1], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - checkTx: false, - expectedErr: nil, - }, - { - name: "pass - enabled msg MsgEthereumTx - blocked msg not wrapped in MsgExec", - msgs: []sdk.Msg{ - &evmtypes.MsgEthereumTx{}, - }, - checkTx: false, - expectedErr: nil, - }, - { - name: "pass - enabled msg - blocked msg not wrapped in MsgExec", - msgs: []sdk.Msg{ - &stakingtypes.MsgCancelUnbondingDelegation{}, - }, - checkTx: false, - expectedErr: nil, - }, - { - name: "pass - enabled msg - MsgGrant contains a non blocked msg", - msgs: []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktypes.MsgSend{})), - &distantFuture, - ), - }, - checkTx: false, - expectedErr: nil, - }, - { - name: "pass - enabled msg - MsgGrant contains a non blocked msg", - msgs: []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - stakingAuthDelegate, - &distantFuture, - ), - }, - checkTx: false, - expectedErr: nil, - }, - { - name: "fail - disabled msg - MsgGrant contains a blocked msg", - msgs: []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - authz.NewGenericAuthorization(sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{})), - &distantFuture, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - MsgGrant contains a blocked msg", - msgs: []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - stakingAuthUndelegate, - &distantFuture, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - MsgGrant contains a blocked msg", - msgs: []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - authz.NewGenericAuthorization(sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{})), - &distantFuture, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "pass - allowed msg - when a MsgExec contains a non blocked msg", - msgs: []sdk.Msg{ - newMsgExec( - testAddresses[1], - []sdk.Msg{banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - )}), - }, - checkTx: false, - expectedErr: nil, - }, - { - name: "fail - disabled msg - MsgExec contains a blocked msg", - msgs: []sdk.Msg{ - newMsgExec( - testAddresses[1], - []sdk.Msg{ - &evmtypes.MsgEthereumTx{}, - }, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - surrounded by valid msgs", - msgs: []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - stakingAuthDelegate, - &distantFuture, - ), - newMsgExec( - testAddresses[1], - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - &evmtypes.MsgEthereumTx{}, - }, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - nested MsgExec containing a blocked msg", - msgs: []sdk.Msg{ - createNestedMsgExec( - testAddresses[1], - 2, - []sdk.Msg{ - &evmtypes.MsgEthereumTx{}, - }, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - nested MsgGrant containing a blocked msg", - msgs: []sdk.Msg{ - newMsgExec( - testAddresses[1], - []sdk.Msg{ - newMsgGrant( - testAddresses[0], - testAddresses[1], - authz.NewGenericAuthorization(sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{})), - &distantFuture, - ), - }, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - nested MsgExec NOT containing a blocked msg but has more nesting levels than the allowed", - msgs: []sdk.Msg{ - createNestedMsgExec( - testAddresses[1], - 6, - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - { - name: "fail - disabled msg - multiple two nested MsgExec messages NOT containing a blocked msg over the limit", - msgs: []sdk.Msg{ - createNestedMsgExec( - testAddresses[1], - 5, - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - ), - createNestedMsgExec( - testAddresses[1], - 5, - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - ), - }, - checkTx: false, - expectedErr: sdkerrors.ErrUnauthorized, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - ctx := sdk.Context{}.WithIsCheckTx(tc.checkTx) - tx, err := createTx(testPrivKeys[0], tc.msgs...) - require.NoError(t, err) - - _, err = decorator.AnteHandle(ctx, tx, false, testutil.NextFn) - if tc.expectedErr != nil { - require.Error(t, err) - require.ErrorIs(t, err, tc.expectedErr) - } else { - require.NoError(t, err) - } - }) - } -} - -func (suite *AnteTestSuite) TestRejectMsgsInAuthz() { - _, testAddresses, err := generatePrivKeyAddressPairs(10) - suite.Require().NoError(err) - - distantFuture := time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC) - - newMsgGrant := func(msgTypeUrl string) *authz.MsgGrant { - msg, err := authz.NewMsgGrant( - testAddresses[0], - testAddresses[1], - authz.NewGenericAuthorization(msgTypeUrl), - &distantFuture, - ) - if err != nil { - panic(err) - } - return msg - } - - msgEthereumTx := evmtypes.NewTx(&evmtypes.EvmTxArgs{ - From: common.BytesToAddress(testAddresses[0]), - ChainID: big.NewInt(constants.TestnetEIP155ChainId), - Nonce: 0, - GasLimit: 100_000, - GasFeeCap: suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx).BigInt(), - GasTipCap: big.NewInt(1), - Input: nil, - Accesses: ðtypes.AccessList{}, - }) - - testcases := []struct { - name string - msgs []sdk.Msg - expectedCode uint32 - isEIP712 bool - }{ - { - name: "fail - a MsgGrant with MsgEthereumTx typeURL on the authorization field is blocked", - msgs: []sdk.Msg{newMsgGrant(sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}))}, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - }, - { - name: "fail - a MsgGrant with MsgCreateVestingAccount typeURL on the authorization field is blocked", - msgs: []sdk.Msg{newMsgGrant(sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}))}, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - }, - { - name: "fail - a MsgGrant with MsgEthereumTx typeURL on the authorization field included on EIP712 tx is blocked", - msgs: []sdk.Msg{newMsgGrant(sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}))}, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - isEIP712: true, - }, - { - name: "fail - a MsgExec with nested messages (valid: MsgSend and invalid: MsgEthereumTx) is blocked", - msgs: []sdk.Msg{ - newMsgExec( - testAddresses[1], - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - msgEthereumTx, - }, - ), - }, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - }, - { - name: "fail - a MsgExec with nested MsgExec messages that has invalid messages is blocked", - msgs: []sdk.Msg{ - createNestedMsgExec( - testAddresses[1], - 2, - []sdk.Msg{ - msgEthereumTx, - }, - ), - }, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - }, - { - name: "fail - a MsgExec with more nested MsgExec messages than allowed and with valid messages is blocked", - msgs: []sdk.Msg{ - createNestedMsgExec( - testAddresses[1], - 6, - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - ), - }, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - }, - { - name: "fail - two MsgExec messages NOT containing a blocked msg but between the two have more nesting than the allowed. Then, is blocked", - msgs: []sdk.Msg{ - createNestedMsgExec( - testAddresses[1], - 5, - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - ), - createNestedMsgExec( - testAddresses[1], - 5, - []sdk.Msg{ - banktypes.NewMsgSend( - testAddresses[0], - testAddresses[3], - sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 100e6)), - ), - }, - ), - }, - expectedCode: sdkerrors.ErrUnauthorized.ABCICode(), - }, - } - - for _, tc := range testcases { - suite.Run(tc.name, func() { - suite.SetupTest() - var ( - tx sdk.Tx - err error - ) - - if tc.isEIP712 { - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - fees := sdk.NewCoins(coinAmount) - cosmosTxArgs := utiltx.CosmosTxArgs{ - TxCfg: suite.clientCtx.TxConfig, - Priv: suite.priv, - ChainID: suite.ctx.ChainID(), - Gas: 200000, - Fees: fees, - Msgs: tc.msgs, - } - - tx, err = utiltx.CreateEIP712CosmosTx( - suite.ctx, - suite.app, - utiltx.EIP712TxArgs{ - CosmosTxArgs: cosmosTxArgs, - }, - ) - } else { - tx, err = createTx(suite.priv, tc.msgs...) - } - suite.Require().NoError(err) - - txEncoder := suite.clientCtx.TxConfig.TxEncoder() - bz, err := txEncoder(tx) - suite.Require().NoError(err) - - resCheckTx, err := suite.app.CheckTx( - &abci.RequestCheckTx{ - Tx: bz, - Type: abci.CheckTxType_New, - }, - ) - suite.Require().NoError(err) - suite.Require().Equal(tc.expectedCode, resCheckTx.Code, resCheckTx.Log) - - header := suite.ctx.BlockHeader() - blockRes, err := suite.app.FinalizeBlock( - &abci.RequestFinalizeBlock{ - Height: header.Height, - Txs: [][]byte{bz}, - Hash: header.AppHash, - NextValidatorsHash: header.NextValidatorsHash, - ProposerAddress: header.ProposerAddress, - Time: header.Time.Add(time.Second), - }, - ) - suite.Require().NoError(err) - suite.Require().Len(blockRes.TxResults, 1) - txRes := blockRes.TxResults[0] - suite.Require().Equal(tc.expectedCode, txRes.Code, txRes.Log) - }) - } -} diff --git a/app/ante/cosmos/fees.go b/app/ante/cosmos/fees.go deleted file mode 100644 index d634c9c1fb..0000000000 --- a/app/ante/cosmos/fees.go +++ /dev/null @@ -1,211 +0,0 @@ -package cosmos - -import ( - "fmt" - "math" - - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - - sdkmath "cosmossdk.io/math" - - errorsmod "cosmossdk.io/errors" - anteutils "github.com/EscanBE/evermint/v12/app/ante/utils" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -// DeductFeeDecorator deducts fees from the first signer of the tx. -// If the first signer does not have the funds to pay for the fees -// then return with InsufficientFunds error. -// The next AnteHandler is called if fees are successfully deducted. -// -// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator -type DeductFeeDecorator struct { - accountKeeper authkeeper.AccountKeeper - bankKeeper bankkeeper.Keeper - distributionKeeper distrkeeper.Keeper - feegrantKeeper authante.FeegrantKeeper - stakingKeeper stakingkeeper.Keeper - txFeeChecker anteutils.TxFeeChecker -} - -// NewDeductFeeDecorator returns a new DeductFeeDecorator. -func NewDeductFeeDecorator( - ak authkeeper.AccountKeeper, - bk bankkeeper.Keeper, - dk distrkeeper.Keeper, - fk authante.FeegrantKeeper, - sk stakingkeeper.Keeper, - tfc anteutils.TxFeeChecker, -) DeductFeeDecorator { - if tfc == nil { - tfc = checkTxFeeWithValidatorMinGasPrices - } - - return DeductFeeDecorator{ - accountKeeper: ak, - bankKeeper: bk, - distributionKeeper: dk, - feegrantKeeper: fk, - stakingKeeper: sk, - txFeeChecker: tfc, - } -} - -// AnteHandle ensures that the transaction contains valid fee requirements and tries to deduct those -// from the account balance. -func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return ctx, errorsmod.Wrap(errortypes.ErrTxDecode, "Tx must be a FeeTx") - } - - if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() <= 0 { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidGasLimit, "must provide positive gas") - } - - var ( - priority int64 - err error - ) - - fee := feeTx.GetFee() - if !simulate { - fee, priority, err = dfd.txFeeChecker(ctx, feeTx) - if err != nil { - return ctx, err - } - } - - feePayer := feeTx.FeePayer() - feeGranter := feeTx.FeeGranter() - - if err = dfd.deductFee(ctx, tx, fee, feePayer, feeGranter); err != nil { - return ctx, err - } - - newCtx := ctx.WithPriority(priority) - - return next(newCtx, tx, simulate) -} - -// deductFee checks if the fee payer has enough funds to pay for the fees and deducts them. -func (dfd DeductFeeDecorator) deductFee(ctx sdk.Context, sdkTx sdk.Tx, fees sdk.Coins, feePayer, feeGranter sdk.AccAddress) error { - if fees.IsZero() { - return nil - } - - if addr := dfd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil { - return fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName) - } - - // by default, deduct fees from feePayer address - deductFeesFrom := feePayer - - // if feegranter is set, then deduct the fee from the feegranter account. - // this works only when feegrant is enabled. - if feeGranter != nil { - if dfd.feegrantKeeper == nil { - return errortypes.ErrInvalidRequest.Wrap("fee grants are not enabled") - } - - if !feeGranter.Equals(feePayer) { - err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fees, sdkTx.GetMsgs()) - if err != nil { - return errorsmod.Wrapf(err, "%s does not not allow to pay fees for %s", feeGranter, feePayer) - } - } - - deductFeesFrom = feeGranter - } - - deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) - if deductFeesFromAcc == nil { - return errortypes.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) - } - - // deduct the fees - if err := authante.DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fees); err != nil { - return errorsmod.Wrapf(err, "failed to deduct fees from: %s", deductFeesFromAcc) - } - - events := sdk.Events{ - sdk.NewEvent( - sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeyFee, fees.String()), - sdk.NewAttribute(sdk.AttributeKeyFeePayer, deductFeesFrom.String()), - ), - } - ctx.EventManager().EmitEvents(events) - - return nil -} - -// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per -// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price. -func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, int64, error) { - feeCoins := feeTx.GetFee() - gas := feeTx.GetGas() - - // Ensure that the provided fees meets a minimum threshold for the validator, - // if this is a CheckTx. This is only for local mempool purposes, and thus - // is only ran on CheckTx. - if ctx.IsCheckTx() { - if err := checkFeeCoinsAgainstMinGasPrices(ctx, feeCoins, gas); err != nil { - return nil, 0, err - } - } - - priority := getTxPriority(feeCoins, int64(gas)) //#nosec G701 -- gosec warning about integer overflow is not relevant here - return feeCoins, priority, nil -} - -// checkFeeCoinsAgainstMinGasPrices checks if the provided fee coins are greater than or equal to the -// required fees, that are based on the minimum gas prices and the gas. If not, it will return an error. -func checkFeeCoinsAgainstMinGasPrices(ctx sdk.Context, feeCoins sdk.Coins, gas uint64) error { - minGasPrices := ctx.MinGasPrices() - if minGasPrices.IsZero() { - return nil - } - - requiredFees := make(sdk.Coins, len(minGasPrices)) - - // Determine the required fees by multiplying each required minimum gas - // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). - glDec := sdkmath.LegacyNewDec(int64(gas)) //#nosec G701 -- gosec warning about integer overflow is not relevant here - for i, gp := range minGasPrices { - fee := gp.Amount.Mul(glDec) - requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) - } - - if !feeCoins.IsAnyGTE(requiredFees) { - return errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) - } - - return nil -} - -// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price -// provided in a transaction. -// NOTE: This implementation should be used with a great consideration as it opens potential attack vectors -// where txs with multiple coins could not be prioritized as expected. -func getTxPriority(fees sdk.Coins, gas int64) int64 { - var priority int64 - for _, c := range fees { - p := int64(math.MaxInt64) - gasPrice := c.Amount.QuoRaw(gas) - if gasPrice.IsInt64() { - p = gasPrice.Int64() - } - if priority == 0 || p < priority { - priority = p - } - } - - return priority -} diff --git a/app/ante/cosmos/fees_test.go b/app/ante/cosmos/fees_test.go deleted file mode 100644 index 2162ef9d7e..0000000000 --- a/app/ante/cosmos/fees_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package cosmos_test - -import ( - "fmt" - - sdkmath "cosmossdk.io/math" - - "cosmossdk.io/x/feegrant" - cosmosante "github.com/EscanBE/evermint/v12/app/ante/cosmos" - "github.com/EscanBE/evermint/v12/constants" - "github.com/EscanBE/evermint/v12/testutil" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - sdktestutil "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (suite *AnteTestSuite) TestDeductFeeDecorator() { - var ( - dfd cosmosante.DeductFeeDecorator - // General setup - addr, priv = testutiltx.NewAccAddressAndKey() - // fee granter - fgAddr, _ = testutiltx.NewAccAddressAndKey() - initBalance = sdkmath.NewInt(1e18) - lowGasPrice = sdkmath.NewInt(1) - zero = sdkmath.ZeroInt() - ) - - // Testcase definitions - testcases := []struct { - name string - balance sdkmath.Int - gas uint64 - gasPrice *sdkmath.Int - feeGranter sdk.AccAddress - checkTx bool - simulate bool - expPass bool - errContains string - postCheck func() - malleate func() - }{ - { - name: "pass - sufficient balance to pay fees", - balance: initBalance, - gas: 0, - checkTx: false, - simulate: true, - expPass: true, - errContains: "", - }, - { - name: "fail - zero gas limit in check tx mode", - balance: initBalance, - gas: 0, - checkTx: true, - simulate: false, - expPass: false, - errContains: "must provide positive gas", - }, - { - name: "fail - checkTx - insufficient funds", - balance: zero, - gas: 10_000_000, - checkTx: true, - simulate: false, - expPass: false, - errContains: "failed to deduct fees from", - postCheck: func() { - // the balance should not have changed - balance := suite.app.BankKeeper.GetBalance(suite.ctx, addr, constants.BaseDenom) - suite.Require().Equal(zero, balance.Amount, "expected balance to be zero") - }, - }, - { - name: "fail - sufficient balance to pay fees but provided fees < required fees", - balance: initBalance, - gas: 10_000_000, - gasPrice: &lowGasPrice, - checkTx: true, - simulate: false, - expPass: false, - errContains: "insufficient fees", - malleate: func() { - suite.ctx = suite.ctx.WithMinGasPrices( - sdk.NewDecCoins( - sdk.NewDecCoin(constants.BaseDenom, sdkmath.NewInt(10_000)), - ), - ) - }, - }, - { - name: "pass - sufficient balance to pay fees & min gas prices is zero", - balance: initBalance, - gas: 10_000_000, - gasPrice: &lowGasPrice, - checkTx: true, - simulate: false, - expPass: true, - errContains: "", - malleate: func() { - suite.ctx = suite.ctx.WithMinGasPrices( - sdk.NewDecCoins( - sdk.NewDecCoin(constants.BaseDenom, zero), - ), - ) - }, - }, - { - name: "pass - sufficient balance to pay fees (fees > required fees)", - balance: initBalance, - gas: 10_000_000, - checkTx: true, - simulate: false, - expPass: true, - errContains: "", - malleate: func() { - suite.ctx = suite.ctx.WithMinGasPrices( - sdk.NewDecCoins( - sdk.NewDecCoin(constants.BaseDenom, sdkmath.NewInt(100)), - ), - ) - }, - }, - { - name: "pass - zero fees", - balance: initBalance, - gas: 100, - gasPrice: &zero, - checkTx: true, - simulate: false, - expPass: true, - errContains: "", - malleate: func() { - suite.ctx = suite.ctx.WithMinGasPrices( - sdk.NewDecCoins( - sdk.NewDecCoin(constants.BaseDenom, zero), - ), - ) - }, - postCheck: func() { - // the tx sender balance should not have changed - balance := suite.app.BankKeeper.GetBalance(suite.ctx, addr, constants.BaseDenom) - suite.Require().Equal(initBalance, balance.Amount, "expected balance to be unchanged") - }, - }, - { - name: "fail - with not authorized fee granter", - balance: initBalance, - gas: 10_000_000, - feeGranter: fgAddr, - checkTx: true, - simulate: false, - expPass: false, - errContains: fmt.Sprintf("%s does not not allow to pay fees for %s", fgAddr, addr), - }, - { - name: "pass - with authorized fee granter", - balance: initBalance, - gas: 10_000_000, - feeGranter: fgAddr, - checkTx: true, - simulate: false, - expPass: true, - errContains: "", - malleate: func() { - // Fund the fee granter - err := testutil.FundAccountWithBaseDenom(suite.ctx, suite.app.BankKeeper, fgAddr, initBalance.Int64()) - suite.Require().NoError(err) - // grant the fees - grant := sdk.NewCoins(sdk.NewCoin( - constants.BaseDenom, initBalance, - )) - err = suite.app.FeeGrantKeeper.GrantAllowance(suite.ctx, fgAddr, addr, &feegrant.BasicAllowance{ - SpendLimit: grant, - }) - suite.Require().NoError(err) - }, - postCheck: func() { - // the tx sender balance should not have changed - balance := suite.app.BankKeeper.GetBalance(suite.ctx, addr, constants.BaseDenom) - suite.Require().Equal(initBalance, balance.Amount, "expected balance to be unchanged") - }, - }, - { - name: "fail - authorized fee granter but no feegrant keeper on decorator", - balance: initBalance, - gas: 10_000_000, - feeGranter: fgAddr, - checkTx: true, - simulate: false, - expPass: false, - errContains: "fee grants are not enabled", - malleate: func() { - // Fund the fee granter - err := testutil.FundAccountWithBaseDenom(suite.ctx, suite.app.BankKeeper, fgAddr, initBalance.Int64()) - suite.Require().NoError(err) - // grant the fees - grant := sdk.NewCoins(sdk.NewCoin( - constants.BaseDenom, initBalance, - )) - err = suite.app.FeeGrantKeeper.GrantAllowance(suite.ctx, fgAddr, addr, &feegrant.BasicAllowance{ - SpendLimit: grant, - }) - suite.Require().NoError(err) - - // remove the feegrant keeper from the decorator - dfd = cosmosante.NewDeductFeeDecorator( - suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.DistrKeeper, nil, *suite.app.StakingKeeper, nil, - ) - }, - }, - } - - // Test execution - for _, tc := range testcases { - suite.Run(tc.name, func() { - suite.SetupTest() - - // Create a new DeductFeeDecorator - dfd = cosmosante.NewDeductFeeDecorator( - suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.DistrKeeper, suite.app.FeeGrantKeeper, *suite.app.StakingKeeper, nil, - ) - - err := testutil.FundAccountWithBaseDenom(suite.ctx, suite.app.BankKeeper, addr, tc.balance.Int64()) - suite.Require().NoError(err) - - // Create an arbitrary message for testing purposes - msg := sdktestutil.NewTestMsg(addr) - - // Set up the transaction arguments - args := testutiltx.CosmosTxArgs{ - TxCfg: suite.clientCtx.TxConfig, - Priv: priv, - Gas: tc.gas, - GasPrice: tc.gasPrice, - FeeGranter: tc.feeGranter, - Msgs: []sdk.Msg{msg}, - } - - if tc.malleate != nil { - tc.malleate() - } - suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx) - - // Create a transaction out of the message - tx, err := testutiltx.PrepareCosmosTx(suite.ctx, suite.app, args) - suite.Require().NoError(err, "failed to create transaction") - - // run the ante handler - _, err = dfd.AnteHandle(suite.ctx, tx, tc.simulate, testutil.NextFn) - - // assert the resulting error - if tc.expPass { - suite.Require().NoError(err, "expected no error") - } else { - suite.Require().Error(err, "expected error") - suite.Require().ErrorContains(err, tc.errContains) - } - - // run the post check - if tc.postCheck != nil { - tc.postCheck() - } - }) - } -} diff --git a/app/ante/cosmos/interfaces.go b/app/ante/cosmos/interfaces.go deleted file mode 100644 index 22cba79039..0000000000 --- a/app/ante/cosmos/interfaces.go +++ /dev/null @@ -1,16 +0,0 @@ -package cosmos - -import sdk "github.com/cosmos/cosmos-sdk/types" - -// BankKeeper defines the exposed interface for using functionality of the bank keeper -// in the context of the cosmos AnteHandler package. -type BankKeeper interface { - GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin - SendCoins(ctx sdk.Context, from, to sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error -} - -type VAuthKeeper interface { - HasProofExternalOwnedAccount(ctx sdk.Context, accAddr sdk.AccAddress) bool -} diff --git a/app/ante/cosmos/min_price.go b/app/ante/cosmos/min_price.go deleted file mode 100644 index 50f4a5023a..0000000000 --- a/app/ante/cosmos/min_price.go +++ /dev/null @@ -1,101 +0,0 @@ -package cosmos - -import ( - "math/big" - - sdkmath "cosmossdk.io/math" - - errorsmod "cosmossdk.io/errors" - evmante "github.com/EscanBE/evermint/v12/app/ante/evm" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" -) - -// MinGasPriceDecorator will check if the transaction's fee is at least as large -// as the MinGasPrices param. If fee is too low, decorator returns error and tx -// is rejected. This applies for both CheckTx and DeliverTx -// If fee is high enough, then call next AnteHandler -// CONTRACT: Tx must implement FeeTx to use MinGasPriceDecorator -type MinGasPriceDecorator struct { - feesKeeper evmante.FeeMarketKeeper - evmKeeper evmante.EVMKeeper -} - -// NewMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for -// Cosmos transactions. -func NewMinGasPriceDecorator(fk evmante.FeeMarketKeeper, ek evmante.EVMKeeper) MinGasPriceDecorator { - return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} -} - -func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected sdk.FeeTx", tx) - } - - feeCoins := feeTx.GetFee() - evmParams := mpd.evmKeeper.GetParams(ctx) - evmDenom := evmParams.GetEvmDenom() - - if !isNoFeeProvidedOrOnlyEvmDenom(feeCoins, evmDenom) { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidCoins, "fee can only be %s", evmDenom) - } - - minGasPrice := mpd.feesKeeper.GetParams(ctx).MinGasPrice - - // Short-circuit if min gas price is 0 or if simulating - if minGasPrice.IsZero() || simulate { - return next(ctx, tx, simulate) - } - - minGasPrices := sdk.DecCoins{ - { - Denom: evmDenom, - Amount: minGasPrice, - }, - } - - gas := feeTx.GetGas() - - requiredFees := make(sdk.Coins, 0) - - // Determine the required fees by multiplying each required minimum gas - // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). - gasLimit := sdkmath.LegacyNewDecFromBigInt(new(big.Int).SetUint64(gas)) - - for _, gp := range minGasPrices { - fee := gp.Amount.Mul(gasLimit).Ceil().RoundInt() - if fee.IsPositive() { - requiredFees = requiredFees.Add(sdk.Coin{Denom: gp.Denom, Amount: fee}) - } - } - - // Fees not provided (or flag "auto"). Then use the base fee to make the check pass - if feeCoins == nil { - return ctx, errorsmod.Wrapf(errortypes.ErrInsufficientFee, - "fee not provided. Please use the --fees flag or the --gas-price flag along with the --gas flag to estimate the fee. The minimun global fee for this tx is: %s", - requiredFees) - } - - if !feeCoins.IsAnyGTE(requiredFees) { - return ctx, errorsmod.Wrapf(errortypes.ErrInsufficientFee, - "provided fee < minimum global fee (%s < %s). Please increase the gas price.", - feeCoins, - requiredFees) - } - - return next(ctx, tx, simulate) -} - -// isNoFeeProvidedOrOnlyEvmDenom returns true if fees is empty or only accept one fee denom which is the evm denom -func isNoFeeProvidedOrOnlyEvmDenom(fees sdk.Coins, evmDenom string) bool { - if len(fees) == 0 { - return true - } - - if len(fees) > 1 { - return false - } - - return fees[0].Denom == evmDenom -} diff --git a/app/ante/cosmos/min_price_test.go b/app/ante/cosmos/min_price_test.go deleted file mode 100644 index 5436a764f0..0000000000 --- a/app/ante/cosmos/min_price_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package cosmos_test - -import ( - "fmt" - - sdkmath "cosmossdk.io/math" - - cosmosante "github.com/EscanBE/evermint/v12/app/ante/cosmos" - "github.com/EscanBE/evermint/v12/constants" - "github.com/EscanBE/evermint/v12/rename_chain/marker" - "github.com/EscanBE/evermint/v12/testutil" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -var execTypes = []struct { - name string - isCheckTx bool - simulate bool -}{ - {"deliverTx", false, false}, - {"deliverTxSimulate", false, true}, -} - -func (suite *AnteTestSuite) TestMinGasPriceDecorator() { - denom := constants.BaseDenom - testMsg := banktypes.MsgSend{ - FromAddress: marker.ReplaceAbleAddress("evm1x8fhpj9nmhqk8z9kpgjt95ck2xwyue0ppeqynn"), - ToAddress: marker.ReplaceAbleAddress("evm1dx67l23hz9l0k9hcher8xz04uj7wf3yuqpfj0p"), - Amount: sdk.Coins{sdk.Coin{Amount: sdkmath.NewInt(10), Denom: denom}}, - } - - testCases := []struct { - name string - malleate func() sdk.Tx - expPass bool - errMsg string - allowPassOnSimulate bool - }{ - { - name: "fail - invalid cosmos tx type", - malleate: func() sdk.Tx { - return &testutiltx.InvalidTx{} - }, - expPass: false, - errMsg: "invalid transaction type", - allowPassOnSimulate: false, - }, - { - name: "pass - valid cosmos tx with MinGasPrices = 0, gasPrice = 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(0), denom, &testMsg) - return txBuilder.GetTx() - }, - expPass: true, - errMsg: "", - allowPassOnSimulate: false, - }, - { - name: "pass - valid cosmos tx with MinGasPrices = 0, gasPrice > 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(10), denom, &testMsg) - return txBuilder.GetTx() - }, - expPass: true, - errMsg: "", - allowPassOnSimulate: false, - }, - { - name: "pass - valid cosmos tx with MinGasPrices = 10, gasPrice = 10", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(10), denom, &testMsg) - return txBuilder.GetTx() - }, - expPass: true, - errMsg: "", - allowPassOnSimulate: false, - }, - { - name: "fail - invalid cosmos tx with MinGasPrices = 10, gasPrice = 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(0), denom, &testMsg) - return txBuilder.GetTx() - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - allowPassOnSimulate: true, - }, - { - name: "invalid cosmos tx with wrong denom", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(10), "stake", &testMsg) - return txBuilder.GetTx() - }, - expPass: false, - errMsg: fmt.Sprintf("fee can only be %s", constants.BaseDenom), - allowPassOnSimulate: false, - }, - { - name: "valid cosmos tx but wrong fee denom", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(10), "stake", &testMsg) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(20)))) - return txBuilder.GetTx() - }, - expPass: false, - errMsg: fmt.Sprintf("fee can only be %s", constants.BaseDenom), - allowPassOnSimulate: false, - }, - { - name: "valid cosmos tx, multiple fee and contains at least one is wrong fee denom", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(10), "stake", &testMsg) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(20)), sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(20)))) - return txBuilder.GetTx() - }, - expPass: false, - errMsg: fmt.Sprintf("fee can only be %s", constants.BaseDenom), - allowPassOnSimulate: false, - }, - { - name: "valid cosmos tx, insufficient fee", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(1), constants.BaseDenom, &testMsg) - return txBuilder.GetTx() - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - allowPassOnSimulate: true, - }, - } - - for _, et := range execTypes { - for _, tc := range testCases { - suite.Run(et.name+"_"+tc.name, func() { - // s.SetupTest(et.isCheckTx) - ctx := suite.ctx.WithIsReCheckTx(et.isCheckTx) - dec := cosmosante.NewMinGasPriceDecorator(suite.app.FeeMarketKeeper, suite.app.EvmKeeper) - _, err := dec.AnteHandle(ctx, tc.malleate(), et.simulate, testutil.NextFn) - - if tc.expPass || (et.simulate && tc.allowPassOnSimulate) { - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Error(err, tc.name) - suite.Require().Contains(err.Error(), tc.errMsg, tc.name) - } - }) - } - } -} diff --git a/app/ante/cosmos/reject_msgs.go b/app/ante/cosmos/reject_msgs.go deleted file mode 100644 index e882997fb6..0000000000 --- a/app/ante/cosmos/reject_msgs.go +++ /dev/null @@ -1,27 +0,0 @@ -package cosmos - -import ( - errorsmod "cosmossdk.io/errors" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" -) - -// RejectMessagesDecorator prevents invalid msg types from being executed -type RejectMessagesDecorator struct{} - -// AnteHandle rejects messages those are prohibited from execution -func (rmd RejectMessagesDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - for _, msg := range tx.GetMsgs() { - switch msg.(type) { - case *evmtypes.MsgEthereumTx: - return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidType, - "MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option", - ) - default: - continue - } - } - return next(ctx, tx, simulate) -} diff --git a/app/ante/cosmos/setup_test.go b/app/ante/cosmos/setup_test.go deleted file mode 100644 index d72dcf4c22..0000000000 --- a/app/ante/cosmos/setup_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package cosmos_test - -import ( - "math" - "testing" - "time" - - storetypes "cosmossdk.io/store/types" - - "github.com/EscanBE/evermint/v12/app/helpers" - "github.com/EscanBE/evermint/v12/constants" - - "github.com/stretchr/testify/suite" - - sdkmath "cosmossdk.io/math" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/client" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - - chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/app/ante" - evmante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" - "github.com/EscanBE/evermint/v12/ethereum/eip712" - "github.com/EscanBE/evermint/v12/testutil" - evertypes "github.com/EscanBE/evermint/v12/types" - "github.com/EscanBE/evermint/v12/x/evm/statedb" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" -) - -type AnteTestSuite struct { - suite.Suite - - ctx sdk.Context - app *chainapp.Evermint - clientCtx client.Context - anteHandler sdk.AnteHandler - ethSigner ethtypes.Signer - priv cryptotypes.PrivKey - enableFeemarket bool - evmParamsOption func(*evmtypes.Params) -} - -const TestGasLimit uint64 = 100000 - -var chainID = constants.TestnetFullChainId - -func (suite *AnteTestSuite) StateDB() *statedb.StateDB { - return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash()))) -} - -func (suite *AnteTestSuite) SetupTest() { - checkTx := false - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) - suite.priv = priv - - suite.app = helpers.EthSetup(checkTx, func(chainApp *chainapp.Evermint, genesis chainapp.GenesisState) chainapp.GenesisState { - { - // setup x/feemarket genesis params - feemarketGenesis := feemarkettypes.DefaultGenesisState() - if !suite.enableFeemarket { - feemarketGenesis.Params.BaseFee = sdkmath.ZeroInt() - feemarketGenesis.Params.MinGasPrice = sdkmath.LegacyZeroDec() - } - err := feemarketGenesis.Validate() - suite.Require().NoError(err) - genesis[feemarkettypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(feemarketGenesis) - } - - { - // setup x/evm genesis params - evmGenesis := evmtypes.DefaultGenesisState() - if suite.evmParamsOption != nil { - suite.evmParamsOption(&evmGenesis.Params) - } - genesis[evmtypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(evmGenesis) - } - - return genesis - }) - - suite.ctx = suite.app.BaseApp.NewContext(checkTx).WithBlockHeader(tmproto.Header{Height: 2, ChainID: chainID, Time: time.Now().UTC()}) - suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(constants.BaseDenom, sdkmath.OneInt()))) - suite.ctx = suite.ctx.WithBlockGasMeter(storetypes.NewGasMeter(1000000000000000000)) - suite.ctx = suite.ctx.WithChainID(chainID) - suite.app.EvmKeeper.WithChainID(suite.ctx) - - stakingParams, err := suite.app.StakingKeeper.GetParams(suite.ctx) - suite.Require().NoError(err) - stakingParams.BondDenom = constants.BaseDenom - err = suite.app.StakingKeeper.SetParams(suite.ctx, stakingParams) - suite.Require().NoError(err) - - infCtx := suite.ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) - err = suite.app.AccountKeeper.Params.Set(infCtx, authtypes.DefaultParams()) - suite.Require().NoError(err) - - encodingConfig := chainapp.RegisterEncodingConfig() - // We're using TestMsg amino encoding in some tests, so register it here. - encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) - eip712.SetEncodingConfig(encodingConfig) - - suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) - - anteHandler := ante.NewAnteHandler(ante.HandlerOptions{ - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - EvmKeeper: suite.app.EvmKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - StakingKeeper: suite.app.StakingKeeper, - IBCKeeper: suite.app.IBCKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - ExtensionOptionChecker: evertypes.HasDynamicFeeExtensionOption, - TxFeeChecker: evmante.NewDynamicFeeChecker(suite.app.EvmKeeper), - }.WithDefaultDisabledAuthzMsgs()) - - suite.anteHandler = anteHandler - suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) - - // fund signer acc to pay for tx fees - amt := sdkmath.NewInt(int64(math.Pow10(18) * 2)) - err = testutil.FundAccount( - suite.ctx, - suite.app.BankKeeper, - suite.priv.PubKey().Address().Bytes(), - sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, amt)), - ) - suite.Require().NoError(err) - - header := suite.ctx.BlockHeader() - suite.ctx = suite.ctx.WithBlockHeight(header.Height - 1) - suite.ctx, err = testutil.Commit(suite.ctx, suite.app, time.Second*0, nil) - suite.Require().NoError(err) -} - -func TestAnteTestSuite(t *testing.T) { - suite.Run(t, &AnteTestSuite{}) -} diff --git a/app/ante/cosmos/utils_test.go b/app/ante/cosmos/utils_test.go deleted file mode 100644 index b283a99bad..0000000000 --- a/app/ante/cosmos/utils_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package cosmos_test - -import ( - "context" - "time" - - chainapp "github.com/EscanBE/evermint/v12/app" - - sdkmath "cosmossdk.io/math" - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" - "github.com/cosmos/cosmos-sdk/client" - clienttx "github.com/cosmos/cosmos-sdk/client/tx" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - "github.com/cosmos/cosmos-sdk/x/authz" -) - -func (suite *AnteTestSuite) CreateTestCosmosTxBuilder(gasPrice sdkmath.Int, denom string, msgs ...sdk.Msg) client.TxBuilder { - txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() - - txBuilder.SetGasLimit(TestGasLimit) - fees := &sdk.Coins{{Denom: denom, Amount: gasPrice.MulRaw(int64(TestGasLimit))}} - txBuilder.SetFeeAmount(*fees) - err := txBuilder.SetMsgs(msgs...) - suite.Require().NoError(err) - return txBuilder -} - -func newMsgExec(grantee sdk.AccAddress, msgs []sdk.Msg) *authz.MsgExec { - msg := authz.NewMsgExec(grantee, msgs) - return &msg -} - -func newMsgGrant(granter sdk.AccAddress, grantee sdk.AccAddress, a authz.Authorization, expiration *time.Time) *authz.MsgGrant { - msg, err := authz.NewMsgGrant(granter, grantee, a, expiration) - if err != nil { - panic(err) - } - return msg -} - -func createNestedMsgExec(a sdk.AccAddress, nestedLvl int, lastLvlMsgs []sdk.Msg) *authz.MsgExec { - msgs := make([]*authz.MsgExec, nestedLvl) - for i := range msgs { - if i == 0 { - msgs[i] = newMsgExec(a, lastLvlMsgs) - continue - } - msgs[i] = newMsgExec(a, []sdk.Msg{msgs[i-1]}) - } - return msgs[nestedLvl-1] -} - -func generatePrivKeyAddressPairs(accCount int) ([]*ethsecp256k1.PrivKey, []sdk.AccAddress, error) { - var ( - err error - testPrivKeys = make([]*ethsecp256k1.PrivKey, accCount) - testAddresses = make([]sdk.AccAddress, accCount) - ) - - for i := range testPrivKeys { - testPrivKeys[i], err = ethsecp256k1.GenerateKey() - if err != nil { - return nil, nil, err - } - testAddresses[i] = testPrivKeys[i].PubKey().Address().Bytes() - } - return testPrivKeys, testAddresses, nil -} - -func createTx(priv cryptotypes.PrivKey, msgs ...sdk.Msg) (sdk.Tx, error) { - encodingConfig := chainapp.RegisterEncodingConfig() - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - - signMode, err := authsigning.APISignModeToInternal(encodingConfig.TxConfig.SignModeHandler().DefaultMode()) - if err != nil { - return nil, err - } - - txBuilder.SetGasLimit(1000000) - if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err - } - - // First round: we gather all the signer infos. We use the "set empty - // signature" hack to do that. - sigV2 := signing.SignatureV2{ - PubKey: priv.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: signMode, - Signature: nil, - }, - Sequence: 0, - } - - sigsV2 := []signing.SignatureV2{sigV2} - - if err := txBuilder.SetSignatures(sigsV2...); err != nil { - return nil, err - } - - signerData := authsigning.SignerData{ - ChainID: chainID, - AccountNumber: 0, - Sequence: 0, - } - sigV2, err = clienttx.SignWithPrivKey( - context.TODO(), - signMode, signerData, - txBuilder, priv, encodingConfig.TxConfig, - 0, - ) - if err != nil { - return nil, err - } - - sigsV2 = []signing.SignatureV2{sigV2} - err = txBuilder.SetSignatures(sigsV2...) - if err != nil { - return nil, err - } - - return txBuilder.GetTx(), nil -} diff --git a/app/ante/cosmos/vesting.go b/app/ante/cosmos/vesting.go deleted file mode 100644 index 066ab83da9..0000000000 --- a/app/ante/cosmos/vesting.go +++ /dev/null @@ -1,49 +0,0 @@ -package cosmos - -import ( - errorsmod "cosmossdk.io/errors" - vauthtypes "github.com/EscanBE/evermint/v12/x/vauth/types" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" -) - -// VestingMessagesAuthorizationDecorator authorize vesting account creation msg execution. -// - If the target account has proof of EOA via `x/vauth`, the message can keep going. -// - Otherwise, the message will be rejected. -type VestingMessagesAuthorizationDecorator struct { - vAuthKeeper VAuthKeeper -} - -// NewVestingMessagesAuthorizationDecorator creates a new VestingMessagesAuthorizationDecorator. -func NewVestingMessagesAuthorizationDecorator(vak VAuthKeeper) VestingMessagesAuthorizationDecorator { - return VestingMessagesAuthorizationDecorator{ - vAuthKeeper: vak, - } -} - -// AnteHandle (read VestingMessagesAuthorizationDecorator) -func (vd VestingMessagesAuthorizationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - for _, msg := range tx.GetMsgs() { - var account string - if m, ok := msg.(*vestingtypes.MsgCreateVestingAccount); ok { - account = m.ToAddress - } else if m, ok := msg.(*vestingtypes.MsgCreatePeriodicVestingAccount); ok { - account = m.ToAddress - } else if m, ok := msg.(*vestingtypes.MsgCreatePermanentLockedAccount); ok { - account = m.ToAddress - } else { - continue - } - - if vd.vAuthKeeper.HasProofExternalOwnedAccount(ctx, sdk.MustAccAddressFromBech32(account)) { - continue - } - - return ctx, errorsmod.Wrapf( - errortypes.ErrUnauthorized, - "must prove account is external owned account (EOA) via `x/%s` module before able to create vesting account: %s", vauthtypes.ModuleName, account, - ) - } - return next(ctx, tx, simulate) -} diff --git a/app/ante/cosmos/vesting_test.go b/app/ante/cosmos/vesting_test.go deleted file mode 100644 index 6edbe450fa..0000000000 --- a/app/ante/cosmos/vesting_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package cosmos_test - -import ( - "encoding/hex" - "fmt" - "time" - - sdkmath "cosmossdk.io/math" - - cosmosante "github.com/EscanBE/evermint/v12/app/ante/cosmos" - "github.com/EscanBE/evermint/v12/constants" - "github.com/EscanBE/evermint/v12/rename_chain/marker" - "github.com/EscanBE/evermint/v12/testutil" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - vauthtypes "github.com/EscanBE/evermint/v12/x/vauth/types" - sdk "github.com/cosmos/cosmos-sdk/types" - vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - "github.com/ethereum/go-ethereum/crypto" -) - -//goland:noinspection ALL -func (suite *AnteTestSuite) TestNewVestingMessagesAuthorizationDecorator() { - proof := vauthtypes.ProofExternalOwnedAccount{ - Account: marker.ReplaceAbleAddress("evm1xx2enpw8wzlr64xkdz2gh3c7epucfdftnqtcem"), - Hash: "0x" + hex.EncodeToString(crypto.Keccak256([]byte(vauthtypes.MessageToSign))), - Signature: "0xe665110439b1d18002ef866285f7e532090065ad74274560db5e8373d0cdb6297afefc70a5dd46c23e74bd3f0f262195f089b2923242a14e8e0791f4b0621a2c00", - } - - submitter := marker.ReplaceAbleAddress("evm1x8fhpj9nmhqk8z9kpgjt95ck2xwyue0ppeqynn") - nonProvedAddress := marker.ReplaceAbleAddress("evm1dx67l23hz9l0k9hcher8xz04uj7wf3yuqpfj0p") - - amount := sdk.NewCoins(sdk.NewInt64Coin(constants.BaseDenom, 1e18)) - - testCases := []struct { - name string - malleate func(ctx sdk.Context) sdk.Tx - expPass bool - errMsg string - }{ - { - name: "pass - invalid cosmos tx type", - malleate: func(_ sdk.Context) sdk.Tx { - return &testutiltx.InvalidTx{} - }, - expPass: true, - }, - { - name: "pass - account has proof", - malleate: func(ctx sdk.Context) sdk.Tx { - suite.app.VAuthKeeper.SaveProofExternalOwnedAccount(ctx, proof) - - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(0), constants.BaseDenom, &vestingtypes.MsgCreateVestingAccount{ - FromAddress: submitter, - ToAddress: proof.Account, - Amount: amount, - EndTime: time.Now().Add(24 * time.Hour).Unix(), - Delayed: true, - }) - return txBuilder.GetTx() - }, - expPass: true, - }, - { - name: "fail - reject account does not have proof", - malleate: func(ctx sdk.Context) sdk.Tx { - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(0), constants.BaseDenom, &vestingtypes.MsgCreateVestingAccount{ - FromAddress: submitter, - ToAddress: nonProvedAddress, - Amount: amount, - EndTime: time.Now().Add(24 * time.Hour).Unix(), - Delayed: true, - }) - return txBuilder.GetTx() - }, - expPass: false, - errMsg: "must prove account is external owned account (EOA)", - }, - } - - execTypes := []struct { - name string - isCheckTx bool - simulate bool - }{ - {"deliverTx", false, false}, - {"deliverTxSimulate", false, true}, - } - - for _, et := range execTypes { - for _, tc := range testCases { - suite.Run(fmt.Sprintf("%s - %s", et.name, tc.name), func() { - // s.SetupTest(et.isCheckTx) - ctx := suite.ctx.WithIsReCheckTx(et.isCheckTx) - dec := cosmosante.NewVestingMessagesAuthorizationDecorator(suite.app.VAuthKeeper) - _, err := dec.AnteHandle(ctx, tc.malleate(ctx), et.simulate, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err, tc.name) - } else { - suite.Require().ErrorContains(err, tc.errMsg) - } - }) - } - } -} diff --git a/app/ante/doc.go b/app/ante/doc.go deleted file mode 100644 index d57d8f32f2..0000000000 --- a/app/ante/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -/* -Package ante defines the SDK auth module's AnteHandler as well as an internal -AnteHandler for an Ethereum transaction (i.e MsgEthereumTx). - -During CheckTx, the transaction is passed through a series of -pre-message execution validation checks such as signature and account -verification in addition to minimum fees being checked. Otherwise, during -DeliverTx, the transaction is simply passed to the EVM which will also -perform the same series of checks. The distinction is made in CheckTx to -prevent spam and DoS attacks. -*/ -package ante diff --git a/app/ante/evm/ante_test.go b/app/ante/evm/ante_test.go deleted file mode 100644 index 8f972dff24..0000000000 --- a/app/ante/evm/ante_test.go +++ /dev/null @@ -1,1394 +0,0 @@ -package evm_test - -import ( - "errors" - "fmt" - "math/big" - "strings" - "time" - - evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" - - "github.com/EscanBE/evermint/v12/constants" - - sdkmath "cosmossdk.io/math" - kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/authz" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - "github.com/ethereum/go-ethereum/core/types" - ethparams "github.com/ethereum/go-ethereum/params" -) - -func (suite *AnteTestSuite) TestAnteHandler() { - var acc sdk.AccountI - addr, privKey := utiltx.NewAddrKey() - to := utiltx.GenerateAddress() - - setup := func() { - suite.enableFeemarket = false - suite.SetupTest() // reset - - acc = suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.Require().NoError(acc.SetSequence(1)) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - - err := suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt(10000000000)) - suite.Require().NoError(err) - - suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, sdkmath.NewInt(100)) - } - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(150), - GasFeeCap: big.NewInt(200), - } - - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - To: &to, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(150), - GasFeeCap: big.NewInt(200), - } - - testCases := []struct { - name string - txFn func() sdk.Tx - checkTx bool - reCheckTx bool - expPass bool - }{ - { - name: "pass - DeliverTx (contract)", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - CheckTx (contract)", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - checkTx: true, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - ReCheckTx (contract)", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: true, - expPass: true, - }, - { - name: "pass - DeliverTx", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - CheckTx", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: true, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - ReCheckTx", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: true, - expPass: true, - }, - { - name: "pass - CheckTx (cosmos tx not signed)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: true, - expPass: true, - }, - { - name: "fail - CheckTx (cosmos tx is not valid)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - // bigger than MaxGasWanted - txBuilder.SetGasLimit(uint64(1 << 63)) - return txBuilder.GetTx() - }, - checkTx: true, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - CheckTx (memo too long)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - txBuilder.SetMemo(strings.Repeat("*", 257)) - return txBuilder.GetTx() - }, - checkTx: true, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - CheckTx (ExtensionOptionsEthereumTx not set)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, true, false) - return txBuilder.GetTx() - }, - checkTx: true, - reCheckTx: false, - expPass: false, - }, - // Based on EVMBackend.SendTransaction, for cosmos tx, forcing null for some fields except ExtensionOptions, Fee, MsgEthereumTx - // should be part of consensus - { - name: "fail - DeliverTx (cosmos tx signed)", - txFn: func() sdk.Tx { - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - To: &to, - Nonce: nonce, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(1), - } - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, true, false) - return tx - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx (cosmos tx with memo)", - txFn: func() sdk.Tx { - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - To: &to, - Nonce: nonce, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(1), - } - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - txBuilder.SetMemo("memo for cosmos tx not allowed") - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx (cosmos tx with timeoutheight)", - txFn: func() sdk.Tx { - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - To: &to, - Nonce: nonce, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(1), - } - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - txBuilder.SetTimeoutHeight(10) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx (invalid fee amount)", - txFn: func() sdk.Tx { - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - To: &to, - Nonce: nonce, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(1), - } - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - - ethTx := signedTx.AsTransaction() - - expFee := evmutils.EthTxFee(ethTx) - invalidFee := new(big.Int).Add(expFee, big.NewInt(1)) - invalidFeeAmount := sdk.Coins{sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewIntFromBigInt(invalidFee))} - txBuilder.SetFeeAmount(invalidFeeAmount) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx (invalid fee gaslimit)", - txFn: func() sdk.Tx { - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - To: &to, - Nonce: nonce, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(1), - } - signedTx := evmtypes.NewTx(ethTxParams) - ethTx := signedTx.AsTransaction() - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - - expGasLimit := ethTx.Gas() - invalidGasLimit := expGasLimit + 1 - txBuilder.SetGasLimit(invalidGasLimit) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "pass - DeliverTx EIP712 signed Cosmos Tx with MsgSend", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))) - amount := sdk.NewCoins(coinAmount) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 create validator", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgCreateValidator(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 create validator (with blank fields)", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgCreateValidator2(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 MsgSubmitProposal", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - gasAmount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - // reusing the gasAmount for deposit - deposit := sdk.NewCoins(coinAmount) - txBuilder, err := suite.CreateTestEIP712SubmitProposal(from, privKey, suite.ctx.ChainID(), gas, gasAmount, deposit) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 MsgGrant", - txFn: func() sdk.Tx { - from := acc.GetAddress() - grantee := sdk.AccAddress("_______grantee______") - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - gasAmount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - blockTime := time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC) - expiresAt := blockTime.Add(time.Hour) - msg, err := authz.NewMsgGrant( - from, grantee, &banktypes.SendAuthorization{SpendLimit: gasAmount}, &expiresAt, - ) - suite.Require().NoError(err) - builder, err := suite.CreateTestEIP712SingleMessageTxBuilder(privKey, suite.ctx.ChainID(), gas, gasAmount, msg) - suite.Require().NoError(err) - - return builder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - - { - name: "success- DeliverTx EIP712 MsgGrantAllowance", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - gasAmount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712GrantAllowance(from, privKey, suite.ctx.ChainID(), gas, gasAmount) - suite.Require().NoError(err) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 edit validator", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgEditValidator(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 submit evidence", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgSubmitEvidence(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 submit proposal v1", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712SubmitProposalV1(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 MsgExec", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgExec(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 MsgVoteV1", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgVoteV1(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 Multiple MsgSend", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MultipleMsgSend(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 Multiple Different Msgs", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MultipleDifferentMsgs(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return suite.TxForTypedData(txBuilder) - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 Same Msgs, Different Schemas", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712SameMsgDifferentSchemas(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return suite.TxForTypedData(txBuilder) - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 Zero Value Array (Should Not Omit Field)", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712ZeroValueArray(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return suite.TxForTypedData(txBuilder) - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 Zero Value Number (Should Not Omit Field)", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712ZeroValueNumber(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return suite.TxForTypedData(txBuilder) - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 MsgTransfer", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgTransfer(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "success- DeliverTx EIP712 MsgTransfer Without Memo", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MsgTransferWithoutMemo(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "fail - DeliverTx EIP712 Multiple Signers", - txFn: func() sdk.Tx { - from := acc.GetAddress() - coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)) - amount := sdk.NewCoins(coinAmount) - gas := uint64(200000) - txBuilder, err := suite.CreateTestEIP712MultipleSignerMsgs(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, fmt.Sprintf("%s_%d-1", constants.ChainIdPrefix, constants.TestnetEIP155ChainId*2 /*modify EIP155 chain id*/), gas, amount) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx EIP712 signed Cosmos Tx with different gas fees", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - txBuilder.SetGasLimit(uint64(300000)) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(30)))) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx EIP712 signed Cosmos Tx with empty signature", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, constants.TestnetFullChainId, gas, amount) - suite.Require().NoError(err) - tx := txBuilder.GetTx() - sigV2 := signing.SignatureV2{ - PubKey: privKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - Signature: nil, - }, - } - err = txBuilder.SetSignatures(sigV2) - return tx - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx EIP712 signed Cosmos Tx with invalid sequence", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - sigsV2 := signing.SignatureV2{ - PubKey: privKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - }, - Sequence: nonce - 1, - } - - err = txBuilder.SetSignatures(sigsV2) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - DeliverTx EIP712 signed Cosmos Tx with invalid signMode", - txFn: func() sdk.Tx { - from := acc.GetAddress() - gas := uint64(200000) - amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))) - txBuilder, err := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, suite.ctx.ChainID(), gas, amount) - suite.Require().NoError(err) - nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) - suite.Require().NoError(err) - sigsV2 := signing.SignatureV2{ - PubKey: privKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: signing.SignMode_SIGN_MODE_UNSPECIFIED, - }, - Sequence: nonce, - } - err = txBuilder.SetSignatures(sigsV2) - suite.Require().NoError(err) - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - invalid from", - txFn: func() sdk.Tx { - msg := evmtypes.NewTx(ethContractCreationTxParams) - tx := suite.CreateTestTx(msg, privKey, 1, false, false) - msg = tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - - msg.From = sdk.AccAddress(func() []byte { - alteredAddr := addr.Bytes()[:] - alteredAddr[0] = (alteredAddr[0] + 1) % 255 - return alteredAddr - }()).String() - return tx - }, - checkTx: true, - reCheckTx: false, - expPass: false, - }, - { - name: "pass - Single-signer EIP-712", - txFn: func() sdk.Tx { - msg := banktypes.NewMsgSend( - sdk.AccAddress(privKey.PubKey().Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSingleSignedTx( - privKey, - signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - msg, - suite.ctx.ChainID(), - 2000000, - "EIP-712", - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - EIP-712 multi-key", - txFn: func() sdk.Tx { - numKeys := 5 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - msg, - suite.ctx.ChainID(), - 2000000, - "EIP-712", - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - Mixed multi-key", - txFn: func() sdk.Tx { - numKeys := 5 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - msg, - suite.ctx.ChainID(), - 2000000, - "mixed", // Combine EIP-712 and standard signatures - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - Mixed multi-key with MsgVote", - txFn: func() sdk.Tx { - numKeys := 5 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := govtypes.NewMsgVote( - sdk.AccAddress(pk.Address()), - 1, - govtypes.OptionYes, - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - msg, - suite.ctx.ChainID(), - 2000000, - "mixed", // Combine EIP-712 and standard signatures - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "fail - Multi-Key with incorrect Chain ID", - txFn: func() sdk.Tx { - numKeys := 5 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, - msg, - fmt.Sprintf("%s_%d-1", constants.ChainIdPrefix, constants.TestnetEIP155ChainId*2 /*modify EIP155 chain id*/), - 2000000, - "mixed", - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - Multi-Key with incorrect sign mode", - txFn: func() sdk.Tx { - numKeys := 5 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_DIRECT, - msg, - suite.ctx.ChainID(), - 2000000, - "mixed", - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - Multi-Key with too little gas", - txFn: func() sdk.Tx { - numKeys := 5 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_DIRECT, - msg, - suite.ctx.ChainID(), - 2000, - "mixed", // Combine EIP-712 and standard signatures - ) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - Multi-Key with different payload than one signed", - txFn: func() sdk.Tx { - numKeys := 1 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_DIRECT, - msg, - suite.ctx.ChainID(), - 2000, - "EIP-712", - ) - - msg.Amount[0].Amount = sdkmath.NewInt(5) - err := txBuilder.SetMsgs(msg) - suite.Require().NoError(err) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - Multi-Key with messages added after signing", - txFn: func() sdk.Tx { - numKeys := 1 - privKeys, pubKeys := suite.GenerateMultipleKeys(numKeys) - pk := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - msg := banktypes.NewMsgSend( - sdk.AccAddress(pk.Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSignedMultisigTx( - privKeys, - signing.SignMode_SIGN_MODE_DIRECT, - msg, - suite.ctx.ChainID(), - 2000, - "EIP-712", - ) - - // Duplicate - err := txBuilder.SetMsgs(msg, msg) - suite.Require().NoError(err) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - Single-Signer EIP-712 with messages added after signing", - txFn: func() sdk.Tx { - msg := banktypes.NewMsgSend( - sdk.AccAddress(privKey.PubKey().Address()), - addr[:], - sdk.NewCoins( - sdk.NewCoin( - constants.BaseDenom, - sdkmath.NewInt(1), - ), - ), - ) - - txBuilder := suite.CreateTestSingleSignedTx( - privKey, - signing.SignMode_SIGN_MODE_DIRECT, - msg, - suite.ctx.ChainID(), - 2000, - "EIP-712", - ) - - err := txBuilder.SetMsgs(msg, msg) - suite.Require().NoError(err) - - return txBuilder.GetTx() - }, - checkTx: false, - reCheckTx: false, - expPass: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - setup() - - suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx) - - // expConsumed := params.TxGasContractCreation + params.TxGas - _, err := suite.anteHandler(suite.ctx, tc.txFn(), false) - - // suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) - - if tc.expPass { - suite.Require().NoError(err) - // suite.Require().Equal(int(expConsumed), int(suite.ctx.GasMeter().GasConsumed())) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *AnteTestSuite) TestAnteHandlerWithDynamicTxFee() { - addr, privKey := utiltx.NewAddrKey() - to := utiltx.GenerateAddress() - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 100000, - GasFeeCap: big.NewInt(ethparams.InitialBaseFee + 1), - GasTipCap: big.NewInt(1), - Accesses: &types.AccessList{}, - } - - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 100000, - GasFeeCap: big.NewInt(ethparams.InitialBaseFee + 1), - GasTipCap: big.NewInt(1), - Accesses: &types.AccessList{}, - To: &to, - } - - testCases := []struct { - name string - txFn func() sdk.Tx - checkTx bool - reCheckTx bool - expPass bool - }{ - { - name: "pass - DeliverTx (contract)", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - CheckTx (contract)", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - checkTx: true, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - ReCheckTx (contract)", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: true, - expPass: true, - }, - { - name: "pass - DeliverTx", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - CheckTx", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: true, - reCheckTx: false, - expPass: true, - }, - { - name: "pass - ReCheckTx", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: true, - expPass: true, - }, - { - name: "pass - CheckTx (cosmos tx not signed)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - checkTx: false, - reCheckTx: true, - expPass: true, - }, - { - name: "fail - CheckTx (cosmos tx is not valid)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - // bigger than MaxGasWanted - txBuilder.SetGasLimit(uint64(1 << 63)) - return txBuilder.GetTx() - }, - checkTx: true, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - CheckTx (memo too long)", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, false, false) - txBuilder.SetMemo(strings.Repeat("*", 257)) - return txBuilder.GetTx() - }, - checkTx: true, - reCheckTx: false, - expPass: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.enableFeemarket = true - suite.SetupTest() // reset - - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.Require().NoError(acc.SetSequence(1)) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - - suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx) - err := suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt((ethparams.InitialBaseFee+10)*100000)) - suite.Require().NoError(err) - - _, err = suite.anteHandler(suite.ctx, tc.txFn(), false) - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } - suite.enableFeemarket = false -} - -func (suite *AnteTestSuite) TestAnteHandlerWithParams() { - addr, privKey := utiltx.NewAddrKey() - to := utiltx.GenerateAddress() - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 100000, - GasFeeCap: big.NewInt(ethparams.InitialBaseFee + 1), - GasTipCap: big.NewInt(1), - Accesses: &types.AccessList{}, - } - - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 100000, - GasFeeCap: big.NewInt(ethparams.InitialBaseFee + 1), - GasTipCap: big.NewInt(1), - Accesses: &types.AccessList{}, - To: &to, - } - - testCases := []struct { - name string - txFn func() sdk.Tx - enableCall bool - enableCreate bool - expErr error - }{ - { - name: "fail - Contract Creation Disabled", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - enableCall: true, - enableCreate: false, - expErr: evmtypes.ErrCreateDisabled, - }, - { - name: "pass - Contract Creation Enabled", - txFn: func() sdk.Tx { - signedContractTx := evmtypes.NewTx(ethContractCreationTxParams) - - tx := suite.CreateTestTx(signedContractTx, privKey, 1, false, false) - return tx - }, - enableCall: true, - enableCreate: true, - expErr: nil, - }, - { - name: "fail - EVM Call Disabled", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - enableCall: false, - enableCreate: true, - expErr: evmtypes.ErrCallDisabled, - }, - { - name: "pass - EVM Call Enabled", - txFn: func() sdk.Tx { - signedTx := evmtypes.NewTx(ethTxParams) - - tx := suite.CreateTestTx(signedTx, privKey, 1, false, false) - return tx - }, - enableCall: true, - enableCreate: true, - expErr: nil, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.evmParamsOption = func(params *evmtypes.Params) { - params.EnableCall = tc.enableCall - params.EnableCreate = tc.enableCreate - } - suite.SetupTest() // reset - - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.Require().NoError(acc.SetSequence(1)) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - - suite.ctx = suite.ctx.WithIsCheckTx(true) - err := suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt((ethparams.InitialBaseFee+10)*100000)) - suite.Require().NoError(err) - - _, err = suite.anteHandler(suite.ctx, tc.txFn(), false) - if tc.expErr == nil { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - suite.Require().True(errors.Is(err, tc.expErr)) - } - }) - } - suite.evmParamsOption = nil -} diff --git a/app/ante/evm/eth.go b/app/ante/evm/eth.go deleted file mode 100644 index c61f91a4c6..0000000000 --- a/app/ante/evm/eth.go +++ /dev/null @@ -1,349 +0,0 @@ -package evm - -import ( - "math" - "math/big" - - evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" - - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - - evertypes "github.com/EscanBE/evermint/v12/types" - evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" - "github.com/EscanBE/evermint/v12/x/evm/statedb" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" -) - -// ExternalOwnedAccountVerificationDecorator validates an account balance checks -type ExternalOwnedAccountVerificationDecorator struct { - ak authkeeper.AccountKeeperI - bk bankkeeper.Keeper - evmKeeper EVMKeeper -} - -// NewExternalOwnedAccountVerificationDecorator creates a new ExternalOwnedAccountVerificationDecorator -func NewExternalOwnedAccountVerificationDecorator(ak authkeeper.AccountKeeperI, bk bankkeeper.Keeper, ek EVMKeeper) ExternalOwnedAccountVerificationDecorator { - return ExternalOwnedAccountVerificationDecorator{ - ak: ak, - bk: bk, - evmKeeper: ek, - } -} - -// AnteHandle validates checks that the sender balance is greater than the total transaction cost. -// The account will be set to store if it doesn't exist, i.e. cannot be found on store. -// This AnteHandler decorator will fail if: -// - any of the msgs is not a MsgEthereumTx -// - from address is empty -// - account balance is lower than the transaction cost -func (avd ExternalOwnedAccountVerificationDecorator) AnteHandle( - ctx sdk.Context, - tx sdk.Tx, - simulate bool, - next sdk.AnteHandler, -) (newCtx sdk.Context, err error) { - if !ctx.IsCheckTx() { - return next(ctx, tx, simulate) - } - - var params *evmtypes.Params - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - - from := msgEthTx.GetFrom() - if from.Empty() { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidAddress, "from address cannot be empty") - } - - // check whether the sender address is EOA - fromAddr := common.BytesToAddress(from) - acct := avd.evmKeeper.GetAccount(ctx, fromAddr) - - if acct == nil { - acc := avd.ak.NewAccountWithAddress(ctx, from) - avd.ak.SetAccount(ctx, acc) - acct = statedb.NewEmptyAccount() - } else if acct.IsContract() { - return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidType, - "the sender is not EOA: address %s, codeHash <%s>", fromAddr, common.BytesToHash(acct.CodeHash), - ) - } - - var spendableBalance *big.Int - if acct.Balance != nil && acct.Balance.Sign() > 0 { - if params == nil { - p := avd.evmKeeper.GetParams(ctx) - params = &p - } - spendableCoin := avd.bk.SpendableCoin(ctx, from, params.EvmDenom) - if spendableCoin.IsNil() || spendableCoin.IsZero() { - spendableBalance = common.Big0 - } else { - spendableBalance = spendableCoin.Amount.BigInt() - } - } else { - spendableBalance = acct.Balance - } - - ethTx := msgEthTx.AsTransaction() - if err := evmkeeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(spendableBalance), ethTx); err != nil { - return ctx, errorsmod.Wrap(err, "failed to check sender balance") - } - } - return next(ctx, tx, simulate) -} - -// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and -// gas consumption. -type EthGasConsumeDecorator struct { - bankKeeper bankkeeper.Keeper - distributionKeeper distrkeeper.Keeper - evmKeeper EVMKeeper - stakingKeeper stakingkeeper.Keeper -} - -// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator -func NewEthGasConsumeDecorator( - bankKeeper bankkeeper.Keeper, - distributionKeeper distrkeeper.Keeper, - evmKeeper EVMKeeper, - stakingKeeper stakingkeeper.Keeper, -) EthGasConsumeDecorator { - return EthGasConsumeDecorator{ - bankKeeper: bankKeeper, - distributionKeeper: distributionKeeper, - evmKeeper: evmKeeper, - stakingKeeper: stakingKeeper, - } -} - -// AnteHandle validates that the Ethereum tx message has enough to cover intrinsic gas -// (during CheckTx only) and that the sender has enough balance to pay for the gas cost. -// -// Intrinsic gas for a transaction is the amount of gas that the transaction uses before the -// transaction is executed. The gas is a constant value plus any cost incurred by additional bytes -// of data supplied with the transaction. -// -// This AnteHandler decorator will fail if: -// - the message is not a MsgEthereumTx -// - sender account cannot be found -// - transaction's gas limit is lower than the intrinsic gas -// - user has neither enough balance to deduct for the transaction fees (gas_limit * gas_price) -// - transaction or block gas meter runs out of gas -// - sets the gas meter limit -// - gas limit is greater than the block gas meter limit -func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - gasWanted := uint64(0) - // gas consumption limit already checked during CheckTx so there's no need to - // verify it again during ReCheckTx - if ctx.IsReCheckTx() { - // Use new context with gasWanted = 0 - // Otherwise, there's an error on txmempool.postCheck (CometBFT) - // that is not bubbled up. Thus, the Tx never runs on DeliverMode - // Error: "gas wanted -1 is negative" - // For more information, see issue #1554 - // https://github.com/evmos/ethermint/issues/1554 - newCtx := ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(gasWanted)) - return next(newCtx, tx, simulate) - } - - evmParams := egcd.evmKeeper.GetParams(ctx) - evmDenom := evmParams.GetEvmDenom() - - var events sdk.Events - - // Use the lowest priority of all the messages as the final one. - minPriority := int64(math.MaxInt64) - baseFee := egcd.evmKeeper.GetBaseFee(ctx) - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - ethTx := msgEthTx.AsTransaction() - - gasWanted += ethTx.Gas() - - fees, err := evmkeeper.VerifyFee(ethTx, evmDenom, baseFee, ctx.IsCheckTx()) - if err != nil { - return ctx, errorsmod.Wrapf(err, "failed to verify the fees") - } - - err = egcd.evmKeeper.DeductTxCostsFromUserBalance(ctx, fees, sdk.MustAccAddressFromBech32(msgEthTx.From)) - if err != nil { - return ctx, errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance") - } - - events = append(events, - sdk.NewEvent( - sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeyFee, fees.String()), - ), - ) - - priority := evmutils.EthTxPriority(ethTx, baseFee) - - if priority < minPriority { - minPriority = priority - } - } - - ctx.EventManager().EmitEvents(events) - - blockGasLimit := evertypes.BlockGasLimit(ctx) - - // return error if the tx gas is greater than the block limit (max gas) - - // NOTE: it's important here to use the gas wanted instead of the gas consumed - // from the tx gas pool. The latter only has the value so far since the - // EthSetupContextDecorator, so it will never exceed the block gas limit. - if gasWanted > blockGasLimit { - return ctx, errorsmod.Wrapf( - errortypes.ErrOutOfGas, - "tx gas (%d) exceeds block gas limit (%d)", - gasWanted, - blockGasLimit, - ) - } - - // Set tx GasMeter with a limit of GasWanted (i.e. gas limit from the Ethereum tx). - // The gas consumed will be then reset to the gas used by the state transition - // in the EVM. - newCtx := ctx. - WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(gasWanted)). - WithPriority(minPriority) - - // we know that we have enough gas on the pool to cover the intrinsic gas - return next(newCtx, tx, simulate) -} - -// CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block -// context rules. -type CanTransferDecorator struct { - evmKeeper EVMKeeper -} - -// NewCanTransferDecorator creates a new CanTransferDecorator instance. -func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { - return CanTransferDecorator{ - evmKeeper: evmKeeper, - } -} - -// AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to -// see if the address can execute the transaction. -func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - params := ctd.evmKeeper.GetParams(ctx) - ethCfg := params.ChainConfig.EthereumConfig(ctd.evmKeeper.ChainID()) - signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight())) - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - - baseFee := ctd.evmKeeper.GetBaseFee(ctx) - - coreMsg, err := msgEthTx.AsMessage(signer, baseFee.BigInt()) - if err != nil { - return ctx, errorsmod.Wrapf( - err, - "failed to create an ethereum core.Message from signer %T", signer, - ) - } - - if coreMsg.GasFeeCap().Cmp(baseFee.BigInt()) < 0 { - return ctx, errorsmod.Wrapf( - errortypes.ErrInsufficientFee, - "max fee per gas less than block base fee (%s < %s)", - coreMsg.GasFeeCap(), baseFee, - ) - } - - // NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below - cfg := &statedb.EVMConfig{ - ChainConfig: ethCfg, - Params: params, - CoinBase: common.Address{}, - BaseFee: baseFee.BigInt(), - } - - stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) - evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB) - - // check that caller has enough balance to cover asset transfer for **topmost** call - // NOTE: here the gas consumed is from the context with the infinite gas meter - if coreMsg.Value().Sign() > 0 && !evm.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { - return ctx, errorsmod.Wrapf( - errortypes.ErrInsufficientFunds, - "failed to transfer %s from address %s using the EVM block context transfer function", - coreMsg.Value(), - coreMsg.From(), - ) - } - } - - return next(ctx, tx, simulate) -} - -// EthIncrementSenderSequenceDecorator increments the sequence of the signers. -type EthIncrementSenderSequenceDecorator struct { - ak authkeeper.AccountKeeperI - ek EVMKeeper -} - -// NewEthIncrementSenderSequenceDecorator creates a new EthIncrementSenderSequenceDecorator. -func NewEthIncrementSenderSequenceDecorator(ak authkeeper.AccountKeeperI, ek EVMKeeper) EthIncrementSenderSequenceDecorator { - return EthIncrementSenderSequenceDecorator{ - ak: ak, - ek: ek, - } -} - -// AnteHandle handles incrementing the sequence of the signer (i.e. sender). If the transaction is a -// contract creation, the nonce will be incremented during the transaction execution and not within -// this AnteHandler decorator. -func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - ethTx := msgEthTx.AsTransaction() - - // increase sequence of sender - acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom()) - if acc == nil { - return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownAddress, - "account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()), - ) - } - nonce := acc.GetSequence() - - // we merged the nonce verification to nonce increment, so when tx includes multiple messages - // with same sender, they'll be accepted. - if ethTx.Nonce() != nonce { - return ctx, errorsmod.Wrapf( - errortypes.ErrInvalidSequence, - "invalid nonce; got %d, expected %d", ethTx.Nonce(), nonce, - ) - } - - if err := acc.SetSequence(nonce + 1); err != nil { - return ctx, errorsmod.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) - } - - issd.ak.SetAccount(ctx, acc) - issd.ek.SetFlagSenderNonceIncreasedByAnteHandle(ctx, true) - } - - return next(ctx, tx, simulate) -} diff --git a/app/ante/evm/eth_test.go b/app/ante/evm/eth_test.go deleted file mode 100644 index 672448bbdd..0000000000 --- a/app/ante/evm/eth_test.go +++ /dev/null @@ -1,1115 +0,0 @@ -package evm_test - -import ( - "math" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - - ethparams "github.com/ethereum/go-ethereum/params" - - storetypes "cosmossdk.io/store/types" - - sdkmath "cosmossdk.io/math" - - "github.com/EscanBE/evermint/v12/constants" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - - ethante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/testutil" - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evertypes "github.com/EscanBE/evermint/v12/types" - "github.com/EscanBE/evermint/v12/x/evm/statedb" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - - ethtypes "github.com/ethereum/go-ethereum/core/types" -) - -func (suite *AnteTestSuite) TestNewExternalOwnedAccountVerificationDecorator() { - dec := ethante.NewExternalOwnedAccountVerificationDecorator( - suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.EvmKeeper, - ) - - addr := utiltx.GenerateAddress() - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 1000, - GasPrice: big.NewInt(1), - } - - tx := evmtypes.NewTx(ethContractCreationTxParams) - - testCases := []struct { - name string - tx sdk.Tx - malleate func(sdk.Context, *statedb.StateDB) - checkTx bool - expPass bool - expPanic bool - }{ - { - name: "not CheckTx", - tx: nil, - malleate: func(_ sdk.Context, _ *statedb.StateDB) {}, - checkTx: false, - expPass: true, - }, - { - name: "invalid transaction type", - tx: &utiltx.InvalidTx{}, - malleate: func(_ sdk.Context, _ *statedb.StateDB) {}, - checkTx: true, - expPass: false, - expPanic: true, - }, - { - name: "sender not set to msg", - tx: tx, - malleate: func(_ sdk.Context, _ *statedb.StateDB) {}, - checkTx: true, - expPass: false, - }, - { - name: "sender not EOA", - tx: tx, - malleate: func(_ sdk.Context, vmdb *statedb.StateDB) { - // set not as an EOA - vmdb.SetCode(addr, []byte("1")) - }, - checkTx: true, - expPass: false, - }, - { - name: "not enough balance to cover tx cost", - tx: tx, - malleate: func(_ sdk.Context, vmdb *statedb.StateDB) { - // reset back to EOA - vmdb.SetCode(addr, nil) - }, - checkTx: true, - expPass: false, - }, - { - name: "success new account", - tx: tx, - malleate: func(_ sdk.Context, vmdb *statedb.StateDB) { - vmdb.AddBalance(addr, big.NewInt(1000000)) - }, - checkTx: true, - expPass: true, - }, - { - name: "success existing account", - tx: tx, - malleate: func(ctx sdk.Context, vmdb *statedb.StateDB) { - acc := suite.app.AccountKeeper.NewAccountWithAddress(ctx, addr.Bytes()) - suite.app.AccountKeeper.SetAccount(ctx, acc) - - vmdb.AddBalance(addr, big.NewInt(1000000)) - }, - checkTx: true, - expPass: true, - }, - { - name: "not enough spendable balance", - tx: tx, - malleate: func(ctx sdk.Context, vmdb *statedb.StateDB) { - acc := suite.app.AccountKeeper.NewAccountWithAddress(ctx, addr.Bytes()) - - const amount = 1_000_000 - - baseVestingAcc := &vestingtypes.BaseVestingAccount{ - BaseAccount: acc.(*authtypes.BaseAccount), - OriginalVesting: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(amount))), - DelegatedFree: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(0))), - DelegatedVesting: sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(0))), - EndTime: ctx.BlockTime().Add(99 * 365 * 24 * time.Hour).Unix(), - } - suite.app.AccountKeeper.SetAccount(ctx, &vestingtypes.DelayedVestingAccount{ - BaseVestingAccount: baseVestingAcc, - }) - - vmdb.AddBalance(addr, big.NewInt(amount)) - }, - checkTx: true, - expPass: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - ctx, _ := suite.ctx.CacheContext() - vmdb := testutil.NewStateDB(ctx, suite.app.EvmKeeper) - tc.malleate(ctx, vmdb) - suite.Require().NoError(vmdb.Commit()) - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(ctx.WithIsCheckTx(tc.checkTx), tc.tx, false, testutil.NextFn) - }) - return - } - - _, err := dec.AnteHandle(ctx.WithIsCheckTx(tc.checkTx), tc.tx, false, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *AnteTestSuite) TestEthNonceVerificationDecorator() { - suite.SetupTest() - dec := ethante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper, suite.app.EvmKeeper) - - addr := utiltx.GenerateAddress() - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 1000, - GasPrice: big.NewInt(1), - } - - tx := evmtypes.NewTx(ethContractCreationTxParams) - - testCases := []struct { - name string - tx sdk.Tx - malleate func() - reCheckTx bool - expPass bool - expPanic bool - }{ - { - name: "fail - ReCheckTx", - tx: &utiltx.InvalidTx{}, - malleate: func() {}, - reCheckTx: true, - expPass: false, - expPanic: true, - }, - { - name: "fail - invalid transaction type", - tx: &utiltx.InvalidTx{}, - malleate: func() {}, - reCheckTx: false, - expPass: false, - expPanic: true, - }, - { - name: "fail - sender account not found", - tx: tx, - malleate: func() {}, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - sender nonce missmatch", - tx: tx, - malleate: func() { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - }, - reCheckTx: false, - expPass: false, - }, - { - name: "pass", - tx: tx, - malleate: func() { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.Require().NoError(acc.SetSequence(1)) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - }, - reCheckTx: false, - expPass: true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - tc.malleate() - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, testutil.NextFn) - }) - return - } - - _, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *AnteTestSuite) TestEthGasConsumeDecorator() { - suite.enableFeemarket = true - suite.SetupTest() - - chainID := suite.app.EvmKeeper.ChainID() - dec := ethante.NewEthGasConsumeDecorator(suite.app.BankKeeper, suite.app.DistrKeeper, suite.app.EvmKeeper, *suite.app.StakingKeeper) - - addr := utiltx.GenerateAddress() - - txGasLimit := uint64(1000) - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: txGasLimit, - GasPrice: big.NewInt(1), - } - - tx := evmtypes.NewTx(ethContractCreationTxParams) - - baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx) - suite.Require().Equal(int64(ethparams.InitialBaseFee), baseFee.Int64()) - - gasPrice := baseFee.BigInt() - - tx2GasLimit := uint64(1000000) - eth2TxContractParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: tx2GasLimit, - GasPrice: gasPrice, - Accesses: ðtypes.AccessList{{Address: addr, StorageKeys: nil}}, - } - tx2 := evmtypes.NewTx(eth2TxContractParams) - tx2Priority := gasPrice.Int64() - - tx3GasLimit := evertypes.BlockGasLimit(suite.ctx) + uint64(1) - eth3TxContractParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: tx3GasLimit, - GasPrice: gasPrice, - Accesses: ðtypes.AccessList{{Address: addr, StorageKeys: nil}}, - } - tx3 := evmtypes.NewTx(eth3TxContractParams) - - dynamicTxContractParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: tx2GasLimit, - GasFeeCap: baseFee.BigInt(), - GasTipCap: big.NewInt(1), - Accesses: ðtypes.AccessList{{Address: addr, StorageKeys: nil}}, - } - dynamicFeeTx := evmtypes.NewTx(dynamicTxContractParams) - dynamicFeeTxPriority := dynamicTxContractParams.GasFeeCap.Int64() - - zeroBalanceAddr := utiltx.GenerateAddress() - zeroBalanceAcc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, zeroBalanceAddr.Bytes()) - suite.app.AccountKeeper.SetAccount(suite.ctx, zeroBalanceAcc) - zeroFeeLegacyTx := evmtypes.NewTx(&evmtypes.EvmTxArgs{ - From: zeroBalanceAddr, - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 1_000_000, - GasPrice: big.NewInt(0), - }) - zeroFeeAccessListTx := evmtypes.NewTx(&evmtypes.EvmTxArgs{ - From: zeroBalanceAddr, - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 1_000_000, - GasPrice: big.NewInt(0), - Accesses: ðtypes.AccessList{{Address: zeroBalanceAddr, StorageKeys: nil}}, - }) - - var vmdb *statedb.StateDB - - testCases := []struct { - name string - tx sdk.Tx - gasLimit uint64 - malleate func(ctx sdk.Context) sdk.Context - expPass bool - expPanic bool - expPriority int64 - postCheck func(ctx sdk.Context) - }{ - { - name: "fail - invalid transaction type", - tx: &utiltx.InvalidTx{}, - gasLimit: math.MaxUint64, - malleate: func(ctx sdk.Context) sdk.Context { return ctx }, - expPass: false, - expPanic: true, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - sender not found", - tx: evmtypes.NewTx(&evmtypes.EvmTxArgs{ - ChainID: chainID, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 1000, - GasPrice: big.NewInt(1), - }), - gasLimit: math.MaxUint64, - malleate: func(ctx sdk.Context) sdk.Context { return ctx }, - expPass: false, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - gas limit too low", - tx: tx, - gasLimit: math.MaxUint64, - malleate: func(ctx sdk.Context) sdk.Context { return ctx }, - expPass: false, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - gas limit above block gas limit", - tx: tx3, - gasLimit: math.MaxUint64, - malleate: func(ctx sdk.Context) sdk.Context { return ctx }, - expPass: false, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - not enough balance for fees", - tx: tx2, - gasLimit: math.MaxUint64, - malleate: func(ctx sdk.Context) sdk.Context { return ctx }, - expPass: false, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - not enough tx gas", - tx: tx2, - gasLimit: 0, - malleate: func(ctx sdk.Context) sdk.Context { - vmdb.AddBalance(addr, big.NewInt(1e6)) - return ctx - }, - expPass: false, - expPanic: true, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - not enough block gas", - tx: tx2, - gasLimit: 0, - malleate: func(ctx sdk.Context) sdk.Context { - vmdb.AddBalance(addr, big.NewInt(1e6)) - return ctx.WithBlockGasMeter(storetypes.NewGasMeter(1)) - }, - expPass: false, - expPanic: true, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "pass - legacy tx", - tx: tx2, - gasLimit: tx2GasLimit, // it's capped - malleate: func(ctx sdk.Context) sdk.Context { - vmdb.AddBalance(addr, big.NewInt(1e16)) - return ctx.WithBlockGasMeter(storetypes.NewGasMeter(1e19)) - }, - expPass: true, - expPanic: false, - expPriority: tx2Priority, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "pass - dynamic fee tx", - tx: dynamicFeeTx, - gasLimit: tx2GasLimit, // it's capped - malleate: func(ctx sdk.Context) sdk.Context { - vmdb.AddBalance(addr, big.NewInt(1e16)) - return ctx.WithBlockGasMeter(storetypes.NewGasMeter(1e19)) - }, - expPass: true, - expPanic: false, - expPriority: dynamicFeeTxPriority, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "pass - gas limit on gasMeter is set on ReCheckTx mode", - tx: dynamicFeeTx, // for reCheckTX mode, gas limit should be set to 0 - gasLimit: 0, - malleate: func(ctx sdk.Context) sdk.Context { - vmdb.AddBalance(addr, big.NewInt(1e15)) - return ctx.WithIsReCheckTx(true) - }, - expPass: true, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "fail - legacy tx - insufficient funds", - tx: tx2, - gasLimit: math.MaxUint64, - malleate: func(ctx sdk.Context) sdk.Context { - return ctx. - WithBlockGasMeter(storetypes.NewGasMeter(1e19)). - WithBlockHeight(ctx.BlockHeight() + 1) - }, - expPass: false, - expPanic: false, - expPriority: tx2Priority, - postCheck: func(ctx sdk.Context) {}, - }, - { - name: "pass - legacy tx - enough funds", - tx: tx2, - gasLimit: tx2GasLimit, // it's capped - malleate: func(ctx sdk.Context) sdk.Context { - err := testutil.FundAccountWithBaseDenom( - ctx, suite.app.BankKeeper, addr.Bytes(), 1e16, - ) - suite.Require().NoError(err) - return ctx. - WithBlockGasMeter(storetypes.NewGasMeter(1e19)). - WithBlockHeight(ctx.BlockHeight() + 1) - }, - expPass: true, - expPanic: false, - expPriority: tx2Priority, - postCheck: func(ctx sdk.Context) { - balance := suite.app.BankKeeper.GetBalance(ctx, sdk.AccAddress(addr.Bytes()), constants.BaseDenom) - suite.Require().True( - balance.Amount.LT(sdkmath.NewInt(1e16)), - "the fees are paid using the available balance, so it should be lower than the initial balance", - ) - }, - }, - { - name: "pass - zero fees (disabled base fee + min gas price) - access list tx", - tx: zeroFeeAccessListTx, - gasLimit: zeroFeeAccessListTx.AsTransaction().Gas(), - malleate: func(ctx sdk.Context) sdk.Context { - suite.zeroBaseFeeAndMinGasPrice(ctx) - return ctx - }, - expPass: true, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) { - finalBalance := suite.app.BankKeeper.GetBalance(ctx, zeroBalanceAddr.Bytes(), constants.BaseDenom) - suite.Require().True(finalBalance.IsZero()) - }, - }, - { - name: "pass - zero fees (disabled base fee + min gas price) - legacy tx", - tx: zeroFeeLegacyTx, - gasLimit: zeroFeeLegacyTx.AsTransaction().Gas(), - malleate: func(ctx sdk.Context) sdk.Context { - suite.zeroBaseFeeAndMinGasPrice(ctx) - return ctx - }, - expPass: true, - expPanic: false, - expPriority: 0, - postCheck: func(ctx sdk.Context) { - finalBalance := suite.app.BankKeeper.GetBalance(ctx, zeroBalanceAddr.Bytes(), constants.BaseDenom) - suite.Require().True(finalBalance.IsZero()) - }, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - cacheCtx, _ := suite.ctx.CacheContext() - // Create new stateDB for each test case from the cached context - vmdb = testutil.NewStateDB(cacheCtx, suite.app.EvmKeeper) - cacheCtx = tc.malleate(cacheCtx) - suite.Require().NoError(vmdb.Commit()) - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(cacheCtx.WithIsCheckTx(true).WithGasMeter(storetypes.NewGasMeter(1)), tc.tx, false, testutil.NextFn) - }) - return - } - - ctx, err := dec.AnteHandle(cacheCtx.WithIsCheckTx(true).WithGasMeter(storetypes.NewInfiniteGasMeter()), tc.tx, false, testutil.NextFn) - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(tc.expPriority, ctx.Priority()) - } else { - suite.Require().Error(err) - } - suite.Require().Equal(tc.gasLimit, ctx.GasMeter().Limit()) - - // check state after the test case - tc.postCheck(ctx) - }) - } -} - -func (suite *AnteTestSuite) TestCanTransferDecorator() { - dec := ethante.NewCanTransferDecorator(suite.app.EvmKeeper) - - addr, privKey := utiltx.NewAddrKey() - - suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, sdkmath.NewInt(100)) - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - GasFeeCap: big.NewInt(200), - GasTipCap: big.NewInt(150), - Accesses: ðtypes.AccessList{}, - } - - tx := evmtypes.NewTx(ethContractCreationTxParams) - - unsignedTxWithoutFrom := evmtypes.NewTx(ethContractCreationTxParams) - unsignedTxWithoutFrom.From = "" - - err := tx.Sign(suite.ethSigner, utiltx.NewSigner(privKey)) - suite.Require().NoError(err) - signedTx := tx - - var vmdb *statedb.StateDB - - testCases := []struct { - name string - tx sdk.Tx - malleate func() - expPass bool - expPanic bool - }{ - { - name: "fail - invalid transaction type", - tx: &utiltx.InvalidTx{}, - malleate: func() {}, - expPass: false, - expPanic: true, - }, - { - name: "fail - AsMessage failed", - tx: unsignedTxWithoutFrom, - malleate: func() {}, - expPass: false, - }, - { - name: "fail - evm CanTransfer failed because insufficient balance", - tx: signedTx, - malleate: func() { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - }, - expPass: false, - }, - { - name: "pass - evm CanTransfer", - tx: signedTx, - malleate: func() { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - - vmdb.AddBalance(addr, big.NewInt(1000000)) - }, - expPass: true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - vmdb = testutil.NewStateDB(suite.ctx, suite.app.EvmKeeper) - tc.malleate() - suite.Require().NoError(vmdb.Commit()) - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, testutil.NextFn) - }) - return - } - - _, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { - dec := ethante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper, suite.app.EvmKeeper) - addr, privKey := utiltx.NewAddrKey() - - ethTxContractParamsNonce0 := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 0, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - } - contract := evmtypes.NewTx(ethTxContractParamsNonce0) - err := contract.Sign(suite.ethSigner, utiltx.NewSigner(privKey)) - suite.Require().NoError(err) - - to := utiltx.GenerateAddress() - ethTxParamsNonce0 := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 0, - To: &to, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - } - tx := evmtypes.NewTx(ethTxParamsNonce0) - err = tx.Sign(suite.ethSigner, utiltx.NewSigner(privKey)) - suite.Require().NoError(err) - - ethTxParamsNonce1 := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - To: &to, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - } - tx2 := evmtypes.NewTx(ethTxParamsNonce1) - err = tx2.Sign(suite.ethSigner, utiltx.NewSigner(privKey)) - suite.Require().NoError(err) - - ethTxParamsNonce2 := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 2, - To: &to, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - } - tx3 := evmtypes.NewTx(ethTxParamsNonce2) - err = tx3.Sign(suite.ethSigner, utiltx.NewSigner(privKey)) - suite.Require().NoError(err) - - testCases := []struct { - name string - tx sdk.Tx - malleate func() - expPass bool - expPanic bool - }{ - { - name: "fail - invalid transaction type", - tx: &utiltx.InvalidTx{}, - malleate: func() {}, - expPass: false, - expPanic: true, - }, - { - name: "fail - no signers", - tx: func() *evmtypes.MsgEthereumTx { - tx := evmtypes.NewTx(ethTxParamsNonce1) - tx.From = "" - return tx - }(), - malleate: func() {}, - expPass: false, - expPanic: true, - }, - { - name: "fail - account not set to store", - tx: tx, - malleate: func() {}, - expPass: false, - expPanic: false, - }, - { - name: "pass - create contract", - tx: contract, - malleate: func() { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - }, - expPass: true, - expPanic: false, - }, - { - name: "pass - call", - tx: tx2, - malleate: func() {}, - expPass: true, - expPanic: false, - }, - { - name: "pass - flag sender nonce increased should be set", - tx: tx3, - malleate: func() { - suite.app.EvmKeeper.SetFlagSenderNonceIncreasedByAnteHandle(suite.ctx, false) - }, - expPass: true, - expPanic: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - tc.malleate() - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx, tc.tx, false, testutil.NextFn) - }) - return - } - - _, err := dec.AnteHandle(suite.ctx, tc.tx, false, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err) - msg := tc.tx.(*evmtypes.MsgEthereumTx) - ethTx := msg.AsTransaction() - - nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, addr) - suite.Equal(ethTx.Nonce()+1, nonce) - suite.True(suite.app.EvmKeeper.IsSenderNonceIncreasedByAnteHandle(suite.ctx), "flag must be set") - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *AnteTestSuite) TestValidateBasicDecorator() { - dec := ethante.NewEthValidateBasicDecorator(suite.app.EvmKeeper) - - addr, privKey := utiltx.NewAddrKey() - - getTx := func(f func(args *evmtypes.EvmTxArgs), skipSign bool) sdk.Tx { - evmTxArgs := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: nil, - GasLimit: 21000, - GasPrice: big.NewInt(1), - GasFeeCap: big.NewInt(200), - GasTipCap: big.NewInt(150), - Accesses: ðtypes.AccessList{}, - } - f(evmTxArgs) - - ethMsg := evmtypes.NewTx(evmTxArgs) - - txBuilder := suite.CreateTestTxBuilder(ethMsg, privKey, 0, false, false, skipSign) - - return txBuilder.GetTx() - } - - testCases := []struct { - name string - tx func() sdk.Tx - expPass bool - expPanic bool - postRunFunc func(sdk.Tx) - }{ - { - name: "fail - invalid transaction type", - tx: func() sdk.Tx { - return &utiltx.InvalidTx{} - }, - expPass: false, - }, - { - name: "pass - accept positive value", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.Amount = big.NewInt(10) - }, false) - }, - expPass: true, - }, - { - name: "pass - accept zero value", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.Amount = big.NewInt(0) - }, false) - }, - expPass: true, - }, - { - name: "pass - accept nil value", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.Amount = nil - }, false) - }, - expPass: true, - }, - { - name: "fail - reject negative value", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.Amount = big.NewInt(-10) - }, false) - }, - expPass: false, - expPanic: true, - }, - { - name: "fail - reject value which more than 256 bits", - tx: func() sdk.Tx { - const skipSign = true // don't sign tx because signing tx also performs validation - return getTx(func(args *evmtypes.EvmTxArgs) { - bz := make([]byte, 33) - bz[0] = 0xFF - args.Amount = new(big.Int).SetBytes(bz) - suite.Require().Less(256, args.Amount.BitLen()) - }, skipSign) - }, - expPass: false, - }, - { - name: "pass - accept positive gas price", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = big.NewInt(10) - }, false) - }, - expPass: true, - }, - { - name: "pass - not reject zero gas price", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = big.NewInt(0) - }, false) - }, - expPass: true, - }, - { - name: "pass - not reject nil gas price", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - }, false) - }, - expPass: true, - }, - { - name: "fail - reject negative gas price", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = big.NewInt(-10) - args.GasFeeCap = nil - args.GasTipCap = nil - }, false) - }, - expPanic: true, - }, - { - name: "pass - auto-correct gas price which more than 256 bits", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - bz := make([]byte, 33) - bz[0] = 0xFF - args.GasPrice = new(big.Int).SetBytes(bz) - suite.Require().Less(256, args.GasPrice.BitLen()) - }, false) - }, - expPass: true, - postRunFunc: func(tx sdk.Tx) { - ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - suite.Require().GreaterOrEqual(256, ethTx.AsTransaction().GasPrice().BitLen()) - }, - }, - { - name: "pass - accept positive gas fee cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasFeeCap = new(big.Int).Add(args.GasTipCap, common.Big1) - }, false) - }, - expPass: true, - }, - { - name: "pass - not reject zero gas fee cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasFeeCap = big.NewInt(0) - args.GasTipCap = big.NewInt(0) - }, false) - }, - expPass: true, - }, - { - name: "pass - not reject nil gas fee cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasFeeCap = nil - args.GasTipCap = nil - }, false) - }, - expPass: true, - }, - { - name: "fail - reject negative gas fee cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasFeeCap = big.NewInt(-10) - }, false) - }, - expPanic: true, - }, - { - name: "fail - reject gas fee cap which more than 256 bits", - tx: func() sdk.Tx { - const skipSign = true // don't sign tx because signing tx also performs validation - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - bz := make([]byte, 33) - bz[0] = 0xFF - args.GasFeeCap = new(big.Int).SetBytes(bz) - suite.Require().Less(256, args.GasFeeCap.BitLen()) - }, skipSign) - }, - expPanic: true, - }, - { - name: "pass - accept positive gas tip cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasTipCap = big.NewInt(10) - }, false) - }, - expPass: true, - }, - { - name: "pass - not reject zero gas tip cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasTipCap = big.NewInt(0) - }, false) - }, - expPass: true, - }, - { - name: "pass - not reject nil gas tip cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - args.GasTipCap = nil - }, false) - }, - expPass: true, - }, - { - name: "fail - reject negative gas tip cap", - tx: func() sdk.Tx { - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - args.GasTipCap = big.NewInt(-10) - }, false) - }, - expPass: false, - expPanic: true, - }, - { - name: "fail - reject gas tip cap which more than 256 bits", - tx: func() sdk.Tx { - const skipSign = true // don't sign tx because signing tx also performs validation - return getTx(func(args *evmtypes.EvmTxArgs) { - args.GasPrice = nil - - bz := make([]byte, 33) - bz[0] = 0xFF - args.GasTipCap = new(big.Int).SetBytes(bz) - suite.Require().Less(256, args.GasTipCap.BitLen()) - }, skipSign) - }, - expPass: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx, tc.tx(), false, testutil.NextFn) - }) - return - } - - tx := tc.tx() - _, err := dec.AnteHandle(suite.ctx, tx, false, testutil.NextFn) - defer func() { - if tc.postRunFunc != nil { - tc.postRunFunc(tx) - } - }() - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/app/ante/evm/fee_checker.go b/app/ante/evm/fee_checker.go deleted file mode 100644 index f9bc307464..0000000000 --- a/app/ante/evm/fee_checker.go +++ /dev/null @@ -1,158 +0,0 @@ -package evm - -import ( - "fmt" - "math" - "math/big" - - cmath "github.com/ethereum/go-ethereum/common/math" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - - anteutils "github.com/EscanBE/evermint/v12/app/ante/utils" - evertypes "github.com/EscanBE/evermint/v12/types" -) - -// NewDynamicFeeChecker returns a `TxFeeChecker` that applies a dynamic fee to -// Cosmos txs using the EIP-1559 fee market logic. -// This can be called in both CheckTx and deliverTx modes. -// a) feeCap = tx.fees / tx.gas -// b) tipFeeCap = tx.MaxPriorityPrice (default) or MaxInt64 -// - when `ExtensionOptionDynamicFeeTx` is omitted, `tipFeeCap` defaults to `MaxInt64`. -// - Tx priority is set to `effectiveGasPrice / DefaultPriorityReduction`. -// - When `x/feemarket` was disabled, it falls back to SDK default behavior (validator min-gas-prices). -func NewDynamicFeeChecker(k DynamicFeeEVMKeeper) anteutils.TxFeeChecker { - return func(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, int64, error) { - if ctx.BlockHeight() == 0 { - // genesis transactions: fallback to min-gas-price logic - return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx) - } - - fees := feeTx.GetFee() - if len(fees) != 1 { - return nil, 0, fmt.Errorf("only one fee coin is allowed, got: %d", len(fees)) - } - - params := k.GetParams(ctx) - denom := params.EvmDenom - - fee := fees[0] - if fee.Denom != denom { - return nil, 0, fmt.Errorf("only '%s' is allowed as fee, got: %s", denom, fee) - } - - baseFee := k.GetBaseFee(ctx) - if baseFee.Sign() != 1 { - // fallback to min-gas-prices logic - return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx) - } - - var gasTipCap *sdkmath.Int - if hasExtOptsTx, ok := feeTx.(authante.HasExtensionOptionsTx); ok { - for _, opt := range hasExtOptsTx.GetExtensionOptions() { - if extOpt, ok := opt.GetCachedValue().(*evertypes.ExtensionOptionDynamicFeeTx); ok { - gasTipCap = &extOpt.MaxPriorityPrice - break - } - } - } - - var effectiveFee sdk.Coins - gas := feeTx.GetGas() - if gasTipCap != nil { // has Dynamic Fee Tx ext - // priority fee cannot be negative - if gasTipCap.IsNegative() { - return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "max priority price cannot be negative") - } - - gasFeeCap := fee.Amount.Quo(sdkmath.NewIntFromUint64(gas)) - - // Compute follow formula of Ethereum EIP-1559 - effectiveGasPrice := cmath.BigMin(new(big.Int).Add(gasTipCap.BigInt(), baseFee.BigInt()), gasFeeCap.BigInt()) - - // Dynamic Fee effective fee = effective gas price * gas - effectiveFee = sdk.Coins{ - sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(effectiveGasPrice).Mul(sdkmath.NewIntFromUint64(gas))), - } - } else { - // normal logic - effectiveFee = fees - } - - if ctx.IsCheckTx() { - // There is case that base fee is too low, - // so during check tx, double check with validator min-gas-config - // to ensure mempool will not be filled up with low fee txs - if _, _, err := checkTxFeeWithValidatorMinGasPrices(ctx, feeTx); err != nil { - // does not pass - return nil, 0, err - } - } - - priority, err := getTxPriority(effectiveFee, int64(gas), baseFee) - if err != nil { - return nil, 0, err - } - return effectiveFee, priority, nil - } -} - -// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per -// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price. -func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coins, int64, error) { - feeCoins := tx.GetFee() - minGasPrices := ctx.MinGasPrices() - gas := int64(tx.GetGas()) //#nosec G701 -- checked for int overflow on ValidateBasic() - - // Ensure that the provided fees meet a minimum threshold for the validator, - // if this is a CheckTx. This is only for local mempool purposes, and thus - // is only ran on check tx. - if ctx.IsCheckTx() && !minGasPrices.IsZero() { - requiredFees := make(sdk.Coins, len(minGasPrices)) - - // Determine the required fees by multiplying each required minimum gas - // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). - glDec := sdkmath.LegacyNewDec(gas) - for i, gp := range minGasPrices { - fee := gp.Amount.Mul(glDec) - requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) - } - - if !feeCoins.IsAnyGTE(requiredFees) { - return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) - } - } - - priority, err := getTxPriority(feeCoins, gas, sdkmath.ZeroInt()) - if err != nil { - return nil, 0, err - } - return feeCoins, priority, nil -} - -// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price -// provided in a transaction. -func getTxPriority(fees sdk.Coins, gas int64, baseFee sdkmath.Int) (int64, error) { - var priority int64 - - for _, fee := range fees { - p := int64(math.MaxInt64) - gasPrice := fee.Amount.QuoRaw(gas) - if gasPrice.LT(baseFee) { - return 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "gas prices too low, got: %s required: %s. Please retry using a higher gas price or a higher fee", gasPrice, baseFee) - } - - if gasPrice.IsInt64() { - p = gasPrice.Int64() - } - if priority == 0 || p < priority { - priority = p - } - } - - return priority, nil -} diff --git a/app/ante/evm/fee_checker_test.go b/app/ante/evm/fee_checker_test.go deleted file mode 100644 index 37cf6a33d6..0000000000 --- a/app/ante/evm/fee_checker_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package evm_test - -import ( - "fmt" - "math/big" - "testing" - - sdkmath "cosmossdk.io/math" - - chainapp "github.com/EscanBE/evermint/v12/app" - evmante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/constants" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/log" - evertypes "github.com/EscanBE/evermint/v12/types" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" -) - -var _ evmante.DynamicFeeEVMKeeper = MockEVMKeeper{} - -type MockEVMKeeper struct { - BaseFee sdkmath.Int -} - -func (m MockEVMKeeper) GetBaseFee(_ sdk.Context) sdkmath.Int { - if m.BaseFee.IsNil() { - return sdkmath.ZeroInt() - } - return m.BaseFee -} - -func (m MockEVMKeeper) GetParams(_ sdk.Context) evmtypes.Params { - return evmtypes.DefaultParams() -} - -func (m MockEVMKeeper) ChainID() *big.Int { - return big.NewInt(constants.TestnetEIP155ChainId) -} - -func TestSDKTxFeeChecker(t *testing.T) { - encodingConfig := chainapp.RegisterEncodingConfig() - minGasPrices := sdk.NewDecCoins(sdk.NewDecCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(10))) - - genesisCtx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - checkTxCtx := sdk.NewContext(nil, tmproto.Header{Height: 1}, true, log.NewNopLogger()).WithMinGasPrices(minGasPrices) - deliverTxCtx := sdk.NewContext(nil, tmproto.Header{Height: 1}, false, log.NewNopLogger()) - - testCases := []struct { - name string - ctx sdk.Context - keeper evmante.DynamicFeeEVMKeeper - buildTx func() sdk.FeeTx - expFees string - expPriority int64 - expSuccess bool - expErrContains string - }{ - { - name: "pass - genesis tx", - ctx: genesisCtx, - keeper: MockEVMKeeper{}, - buildTx: func() sdk.FeeTx { - return encodingConfig.TxConfig.NewTxBuilder().GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: true, - }, - { - name: "fail - no fee provided", - ctx: checkTxCtx, - keeper: MockEVMKeeper{}, - buildTx: func() sdk.FeeTx { - return encodingConfig.TxConfig.NewTxBuilder().GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "only one fee coin is allowed", - }, - { - name: "pass - min-gas-prices", - ctx: checkTxCtx, - keeper: MockEVMKeeper{}, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "pass - min-gas-prices deliverTx", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{}, - buildTx: func() sdk.FeeTx { - return encodingConfig.TxConfig.NewTxBuilder().GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: true, - }, - { - name: "fail - gas price is zero, lower than base fee", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(2), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1)))) - return txBuilder.GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "Please retry using a higher gas price or a higher fee", - }, - { - name: "pass - dynamic fee", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "fail - reject multi fee coins", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins( - sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)), - sdk.NewCoin(constants.BaseDenom+"x", sdkmath.NewInt(10)), - )) - return txBuilder.GetTx() - }, - expSuccess: false, - expErrContains: "only one fee coin is allowed, got: 2", - }, - { - name: "fail - reject invalid denom fee coin", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins( - sdk.NewCoin(constants.BaseDenom+"x", sdkmath.NewInt(10)), - )) - return txBuilder.GetTx() - }, - expSuccess: false, - expErrContains: fmt.Sprintf("only '%s' is allowed as fee, got:", constants.BaseDenom), - }, - { - name: "pass - dynamic fee priority", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))) - return txBuilder.GetTx() - }, - expFees: "20" + constants.BaseDenom, - expPriority: 20, - expSuccess: true, - }, - { - name: "pass - dynamic fee empty tipFeeCap", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(10)))) - - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{}) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "pass - dynamic fee tipFeeCap", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(10)))) - - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ - MaxPriorityPrice: sdkmath.NewInt(5), - }) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "fail - negative dynamic fee tipFeeCap", - ctx: deliverTxCtx, - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))) - - // set negative priority fee - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ - MaxPriorityPrice: sdkmath.NewInt(-5), - }) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "max priority price cannot be negative", - }, - { - name: "fail - low fee txs will not reach mempool due to min-gas-prices by validator", - ctx: sdk.NewContext(nil, tmproto.Header{Height: 1}, true, log.NewNopLogger()). - WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1e9)))), - keeper: MockEVMKeeper{ - BaseFee: sdkmath.NewInt(1), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1_000_000)))) - - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ - MaxPriorityPrice: sdkmath.NewInt(5), - }) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: fmt.Sprintf("insufficient fees; got: 1000000%s required: 1000000000%s: insufficient fee", constants.BaseDenom, constants.BaseDenom), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - fees, priority, err := evmante.NewDynamicFeeChecker(tc.keeper)(tc.ctx, tc.buildTx()) - if tc.expSuccess { - require.Equal(t, tc.expFees, fees.String()) - require.Equal(t, tc.expPriority, priority) - } else { - require.Error(t, err) - require.NotEmpty(t, tc.expErrContains, err.Error()) - require.ErrorContains(t, err, tc.expErrContains) - } - }) - } -} diff --git a/app/ante/evm/fees.go b/app/ante/evm/fees.go deleted file mode 100644 index f18c0b4546..0000000000 --- a/app/ante/evm/fees.go +++ /dev/null @@ -1,124 +0,0 @@ -package evm - -import ( - "math/big" - - evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" - - sdkmath "cosmossdk.io/math" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" -) - -// EthMinGasPriceDecorator will check if the transaction's fee is at least as large -// as the MinGasPrices param. If fee is too low, decorator returns error and tx is rejected. -// This applies to both CheckTx and DeliverTx and regardless -// fee market params (EIP-1559) are enabled. -// If fee is high enough, then call next AnteHandler -type EthMinGasPriceDecorator struct { - feesKeeper FeeMarketKeeper - evmKeeper EVMKeeper -} - -// NewEthMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for -// Ethereum transactions. -func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator { - return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} -} - -// AnteHandle ensures that the effective fee from the transaction is greater than the -// minimum global fee, which is defined by the MinGasPrice (parameter) * GasLimit (tx argument). -func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - minGasPrice := empd.feesKeeper.GetParams(ctx).MinGasPrice - - // short-circuit if min gas price is 0 - if minGasPrice.IsZero() { - return next(ctx, tx, simulate) - } - - baseFee := empd.evmKeeper.GetBaseFee(ctx) - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - - // For dynamic transactions, GetFee() uses the GasFeeCap value, which - // is the maximum gas price that the signer can pay. In practice, the - // signer can pay less, if the block's BaseFee is lower. So, in this case, - // we use the EffectiveFee. If the feemarket formula results in a BaseFee - // that lowers EffectivePrice until it is < MinGasPrices, the users must - // increase the GasTipCap (priority fee) until EffectivePrice > MinGasPrices. - // Transactions with MinGasPrices * gasUsed < tx fees < EffectiveFee are rejected - // by the feemarket AnteHandle - - ethTx := msgEthTx.AsTransaction() - feeAmt := evmutils.EthTxEffectiveFee(ethTx, baseFee) - - gasLimit := sdkmath.LegacyNewDecFromBigInt(new(big.Int).SetUint64(ethTx.Gas())) - - requiredFee := minGasPrice.Mul(gasLimit) - fee := sdkmath.LegacyNewDecFromBigInt(feeAmt) - - if fee.LT(requiredFee) { - return ctx, errorsmod.Wrapf( - errortypes.ErrInsufficientFee, - "provided fee < minimum global fee (%s < %s). Please increase the priority tip (for EIP-1559 txs) or the gas prices (for access list or legacy txs)", //nolint:lll - fee.TruncateInt().String(), requiredFee.TruncateInt().String(), - ) - } - } - - return next(ctx, tx, simulate) -} - -// EthMempoolFeeDecorator will check if the transaction's effective fee is at least as large -// as the local validator's minimum gasFee (defined in validator config). -// If fee is too low, decorator returns error and tx is rejected from mempool. -// Note this only applies when ctx.CheckTx = true -// If fee is high enough or not CheckTx, then call next AnteHandler -// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator -type EthMempoolFeeDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthMempoolFeeDecorator creates a new NewEthMempoolFeeDecorator instance used only for -// Ethereum transactions. -func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator { - return EthMempoolFeeDecorator{ - evmKeeper: ek, - } -} - -// AnteHandle ensures that the provided fees meet a minimum threshold for the validator. -// This check only for local mempool purposes, and thus it is only run on (Re)CheckTx. -// TODO: remove the duplicated logic in the DynamicFeeCheck -func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - if !ctx.IsCheckTx() || simulate { - return next(ctx, tx, simulate) - } - - evmParams := mfd.evmKeeper.GetParams(ctx) - evmDenom := evmParams.GetEvmDenom() - minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom) - - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - ethTx := msgEthTx.AsTransaction() - - fee := sdkmath.LegacyNewDecFromBigInt(evmutils.EthTxFee(ethTx)) - - gasLimit := sdkmath.LegacyNewDecFromBigInt(new(big.Int).SetUint64(ethTx.Gas())) - requiredFee := minGasPrice.Mul(gasLimit) - - if fee.LT(requiredFee) { - return ctx, errorsmod.Wrapf( - errortypes.ErrInsufficientFee, - "insufficient fee; got: %s required: %s", - fee, requiredFee, - ) - } - - return next(ctx, tx, simulate) -} diff --git a/app/ante/evm/fees_test.go b/app/ante/evm/fees_test.go deleted file mode 100644 index d7b33daf35..0000000000 --- a/app/ante/evm/fees_test.go +++ /dev/null @@ -1,314 +0,0 @@ -package evm_test - -import ( - "math" - "math/big" - - "github.com/EscanBE/evermint/v12/rename_chain/marker" - - sdkmath "cosmossdk.io/math" - evmante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/testutil" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - ethtypes "github.com/ethereum/go-ethereum/core/types" -) - -var execTypes = []struct { - name string - isCheckTx bool - simulate bool -}{ - {"deliverTx", false, false}, - {"deliverTxSimulate", false, true}, -} - -func (suite *AnteTestSuite) TestEthMinGasPriceDecorator() { - denom := evmtypes.DefaultEVMDenom - from, privKey := testutiltx.NewAddrKey() - to := testutiltx.GenerateAddress() - emptyAccessList := ethtypes.AccessList{} - - testCases := []struct { - name string - malleate func() sdk.Tx - expPass bool - expPanic bool - errMsg string - }{ - { - name: "invalid tx type", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - return &testutiltx.InvalidTx{} - }, - expPass: false, - expPanic: true, - errMsg: "invalid message type", - }, - { - name: "wrong tx type", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - testMsg := banktypes.MsgSend{ - FromAddress: marker.ReplaceAbleAddress("evm1x8fhpj9nmhqk8z9kpgjt95ck2xwyue0ppeqynn"), - ToAddress: marker.ReplaceAbleAddress("evm1dx67l23hz9l0k9hcher8xz04uj7wf3yuqpfj0p"), - Amount: sdk.Coins{sdk.Coin{Amount: sdkmath.NewInt(10), Denom: denom}}, - } - txBuilder := suite.CreateTestCosmosTxBuilder(sdkmath.NewInt(0), denom, &testMsg) - return txBuilder.GetTx() - }, - expPass: false, - expPanic: true, - errMsg: "invalid message type", - }, - { - name: "valid: invalid tx type with MinGasPrices = 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - return &testutiltx.InvalidTx{} - }, - expPass: true, - errMsg: "", - }, - { - name: "valid legacy tx with MinGasPrices = 0, gasPrice = 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(0), nil, nil, nil) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "valid legacy tx with MinGasPrices = 0, gasPrice > 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(10), nil, nil, nil) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "valid legacy tx with MinGasPrices = 10, gasPrice = 10", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(10), nil, nil, nil) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "invalid legacy tx with MinGasPrices = 10, gasPrice = 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), big.NewInt(0), nil, nil, nil) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - }, - { - name: "valid dynamic tx with MinGasPrices = 0, EffectivePrice = 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(0), big.NewInt(0), &emptyAccessList) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "valid dynamic tx with MinGasPrices = 0, EffectivePrice > 0", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(100), big.NewInt(50), &emptyAccessList) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "valid dynamic tx with MinGasPrices < EffectivePrice", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(100), big.NewInt(100), &emptyAccessList) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "invalid dynamic tx with MinGasPrices > EffectivePrice", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(10) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(0), big.NewInt(0), &emptyAccessList) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - }, - { - name: "invalid dynamic tx with MinGasPrices > BaseFee, MinGasPrices > EffectivePrice", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(100) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - feemarketParams := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - feemarketParams.BaseFee = sdkmath.NewInt(10) - err = suite.app.FeeMarketKeeper.SetParams(suite.ctx, feemarketParams) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(1000), big.NewInt(0), &emptyAccessList) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - }, - { - name: "valid dynamic tx with MinGasPrices > BaseFee, MinGasPrices < EffectivePrice (big GasTipCap)", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(100) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - feemarketParams := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - feemarketParams.BaseFee = sdkmath.NewInt(10) - err = suite.app.FeeMarketKeeper.SetParams(suite.ctx, feemarketParams) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx(from, to, nil, make([]byte, 0), nil, big.NewInt(1000), big.NewInt(101), &emptyAccessList) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: true, - errMsg: "", - }, - { - name: "do not panic when tx fee overflow of int64", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(math.MaxInt64).MulInt64(2) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - gasFeeOverflowInt64 := new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)) - msg := suite.BuildTestEthTx( - from, // from - to, // to - nil, // amount - make([]byte, 0), // input - nil, // gas price - gasFeeOverflowInt64, // gas fee cap - gasFeeOverflowInt64, // gas tip cap - &emptyAccessList, // access list - ) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - }, - { - name: "do not panic when required fee (minimum global fee) overflow of int64", - malleate: func() sdk.Tx { - params := suite.app.FeeMarketKeeper.GetParams(suite.ctx) - params.MinGasPrice = sdkmath.LegacyNewDec(math.MaxInt64).MulInt64(2) - err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - msg := suite.BuildTestEthTx( - from, // from - to, // to - nil, // amount - make([]byte, 0), // input - nil, // gas price - big.NewInt(100_000), // gas fee cap - big.NewInt(100), // gas tip cap - &emptyAccessList, // access list - ) - return suite.CreateTestTx(msg, privKey, 1, false, false) - }, - expPass: false, - errMsg: "provided fee < minimum global fee", - }, - } - - for _, et := range execTypes { - for _, tc := range testCases { - suite.Run(et.name+"_"+tc.name, func() { - // s.SetupTest(et.isCheckTx) - suite.SetupTest() - dec := evmante.NewEthMinGasPriceDecorator(suite.app.FeeMarketKeeper, suite.app.EvmKeeper) - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx, tc.malleate(), et.simulate, testutil.NextFn) - }) - return - } - - _, err := dec.AnteHandle(suite.ctx, tc.malleate(), et.simulate, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Error(err, tc.name) - suite.Require().Contains(err.Error(), tc.errMsg, tc.name) - } - }) - } - } -} - -func (suite *AnteTestSuite) TestEthMempoolFeeDecorator() { - // TODO: add test -} diff --git a/app/ante/evm/interfaces.go b/app/ante/evm/interfaces.go deleted file mode 100644 index a07b3af65b..0000000000 --- a/app/ante/evm/interfaces.go +++ /dev/null @@ -1,46 +0,0 @@ -package evm - -import ( - "math/big" - - sdkmath "cosmossdk.io/math" - - "github.com/EscanBE/evermint/v12/x/evm/statedb" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdktxtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/vm" -) - -// EVMKeeper defines the expected keeper interface used on the AnteHandler -type EVMKeeper interface { //nolint: revive - statedb.Keeper - DynamicFeeEVMKeeper - - NewEVM(ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM - DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from sdk.AccAddress) error - GetBalance(ctx sdk.Context, addr common.Address) *big.Int - SetupExecutionContext(ctx sdk.Context, txGas uint64, txType uint8) sdk.Context - GetTxCountTransient(ctx sdk.Context) uint64 - GetParams(ctx sdk.Context) evmtypes.Params - - SetFlagSenderNonceIncreasedByAnteHandle(ctx sdk.Context, increased bool) -} - -type FeeMarketKeeper interface { - GetParams(ctx sdk.Context) (params feemarkettypes.Params) -} - -// DynamicFeeEVMKeeper is a subset of EVMKeeper interface that supports dynamic fee checker -type DynamicFeeEVMKeeper interface { - ChainID() *big.Int - GetParams(ctx sdk.Context) evmtypes.Params - GetBaseFee(ctx sdk.Context) sdkmath.Int -} - -type protoTxProvider interface { - GetProtoTx() *sdktxtypes.Tx -} diff --git a/app/ante/evm/setup_ctx.go b/app/ante/evm/setup_ctx.go deleted file mode 100644 index 26041e76d1..0000000000 --- a/app/ante/evm/setup_ctx.go +++ /dev/null @@ -1,218 +0,0 @@ -package evm - -import ( - "errors" - "strconv" - - evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" - - storetypes "cosmossdk.io/store/types" - - "github.com/EscanBE/evermint/v12/utils" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" -) - -// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption -// by setting the gas meter to infinite -type EthSetupContextDecorator struct { - evmKeeper EVMKeeper -} - -func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator { - return EthSetupContextDecorator{ - evmKeeper: evmKeeper, - } -} - -func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // all transactions must implement GasTx - _, ok := tx.(authante.GasTx) - if !ok { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected GasTx", tx) - } - - // We need to setup an empty gas config so that the gas is consistent with Ethereum. - newCtx = ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) - newCtx = utils.UseZeroGasConfig(newCtx) - - // reset previous run - esc.evmKeeper.SetFlagSenderNonceIncreasedByAnteHandle(newCtx, false) - - return next(newCtx, tx, simulate) -} - -// SingleEthTxDecorator check if the transaction contains one and only one EthereumTx -type SingleEthTxDecorator struct{} - -func NewSingleEthTxDecorator() SingleEthTxDecorator { - return SingleEthTxDecorator{} -} - -func (sed SingleEthTxDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - for _, msg := range tx.GetMsgs() { - if _, isEthTx := msg.(*evmtypes.MsgEthereumTx); !isEthTx { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) - } - } - - if len(tx.GetMsgs()) != 1 { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "expected one and only one %T", (*evmtypes.MsgEthereumTx)(nil)) - } - - return next(ctx, tx, simulate) -} - -// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit). -type EthEmitEventDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthEmitEventDecorator creates a new EthEmitEventDecorator -func NewEthEmitEventDecorator(evmKeeper EVMKeeper) EthEmitEventDecorator { - return EthEmitEventDecorator{evmKeeper} -} - -// AnteHandle emits some basic events for the eth messages -func (eeed EthEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc, - // we need to emit some basic events at the very end of ante handler to be indexed by CometBFT. - txIndex := eeed.evmKeeper.GetTxCountTransient(ctx) - 1 - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - ethTx := msgEthTx.AsTransaction() - - // emit ethereum tx hash as an event so that it can be indexed by CometBFT for query purposes - // it's emitted in ante handler, so we can query failed transaction (out of block gas limit). - ctx.EventManager().EmitEvent(sdk.NewEvent( - evmtypes.EventTypeEthereumTx, - sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, ethTx.Hash().Hex()), - sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)), // #nosec G701 - )) - } - - return next(ctx, tx, simulate) -} - -// EthSetupExecutionDecorator update some information to transient store. -type EthSetupExecutionDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthSetupExecutionDecorator creates a new EthSetupExecutionDecorator -func NewEthSetupExecutionDecorator(evmKeeper EVMKeeper) EthSetupExecutionDecorator { - return EthSetupExecutionDecorator{evmKeeper} -} - -// AnteHandle emits some basic events for the eth messages -func (sed EthSetupExecutionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() - sed.evmKeeper.SetupExecutionContext(ctx, ethTx.Gas(), ethTx.Type()) - return next(ctx, tx, simulate) -} - -// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures -type EthValidateBasicDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator -func NewEthValidateBasicDecorator(ek EVMKeeper) EthValidateBasicDecorator { - return EthValidateBasicDecorator{ - evmKeeper: ek, - } -} - -// AnteHandle handles basic validation of tx -func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // no need to validate basic on recheck tx, call next AnteHandler - if ctx.IsReCheckTx() { - return next(ctx, tx, simulate) - } - - err := tx.(sdk.HasValidateBasic).ValidateBasic() - // ErrNoSignatures is fine with eth tx - if err != nil && !errors.Is(err, errortypes.ErrNoSignatures) { - return ctx, errorsmod.Wrap(err, "tx basic validation failed") - } - - // For eth type cosmos tx, some fields should be verified as zero values, - // since we will only verify the signature against the hash of the MsgEthereumTx.Data - wrapperTx, ok := tx.(protoTxProvider) - if !ok { - return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx) - } - - protoTx := wrapperTx.GetProtoTx() - body := protoTx.Body - if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, - "for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty") - } - - if len(body.ExtensionOptions) != 1 { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") - } - - authInfo := protoTx.AuthInfo - if len(authInfo.SignerInfos) > 0 { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty") - } - - if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") - } - - sigs := protoTx.Signatures - if len(sigs) > 0 { - return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx Signatures should be empty") - } - - txFee := sdk.Coins{} - txGasLimit := uint64(0) - - evmParams := vbd.evmKeeper.GetParams(ctx) - enableCreate := evmParams.GetEnableCreate() - enableCall := evmParams.GetEnableCall() - evmDenom := evmParams.GetEvmDenom() - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - - err := msgEthTx.ValidateBasic() - if err != nil { - return ctx, errorsmod.Wrap(err, "msg basic validation failed") - } - - ethTx := msgEthTx.AsTransaction() - - txGasLimit += ethTx.Gas() - - // return error if contract creation or call are disabled through governance - if !enableCreate && ethTx.To() == nil { - return ctx, errorsmod.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") - } else if !enableCall && ethTx.To() != nil { - return ctx, errorsmod.Wrap(evmtypes.ErrCallDisabled, "failed to call contract") - } - - txFee = txFee.Add( - sdk.NewCoin(evmDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxFee(ethTx))), - ) - } - - if !authInfo.Fee.Amount.Equal(txFee) { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee) - } - - if authInfo.Fee.GasLimit != txGasLimit { - return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit) - } - - return next(ctx, tx, simulate) -} diff --git a/app/ante/evm/setup_ctx_test.go b/app/ante/evm/setup_ctx_test.go deleted file mode 100644 index c1631e4880..0000000000 --- a/app/ante/evm/setup_ctx_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package evm_test - -import ( - "math" - "math/big" - - evmante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/testutil" - - storetypes "cosmossdk.io/store/types" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (suite *AnteTestSuite) TestEthSetupContextDecorator() { - dec := evmante.NewEthSetUpContextDecorator(suite.app.EvmKeeper) - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 1000, - GasPrice: big.NewInt(1), - } - tx := evmtypes.NewTx(ethContractCreationTxParams) - - testCases := []struct { - name string - tx sdk.Tx - malleate func() - expPass bool - }{ - { - name: "fail - invalid transaction type - does not implement GasTx", - tx: &testutiltx.InvalidTx{}, - expPass: false, - }, - { - name: "pass - transaction implement GasTx", - tx: tx, - expPass: true, - }, - { - name: "pass - gas config should be empty", - tx: tx, - malleate: func() { - suite.ctx = suite.ctx.WithKVGasConfig(storetypes.GasConfig{ - WriteCostFlat: 1000, - }) - suite.ctx = suite.ctx.WithTransientKVGasConfig(storetypes.GasConfig{ - WriteCostFlat: 1000, - }) - }, - expPass: true, - }, - { - name: "pass - gas meter should be infinite", - tx: tx, - malleate: func() { - suite.ctx = suite.ctx.WithGasMeter(storetypes.NewGasMeter(1_000)) - }, - expPass: true, - }, - { - name: "pass - flag nonce set by ante handle should be removed", - tx: tx, - malleate: func() { - suite.app.EvmKeeper.SetFlagSenderNonceIncreasedByAnteHandle(suite.ctx, true) - }, - expPass: true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - if tc.malleate != nil { - tc.malleate() - } - - newCtx, err := dec.AnteHandle(suite.ctx, tc.tx, false, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err) - suite.Equal(storetypes.GasConfig{}, newCtx.KVGasConfig()) - suite.Equal(storetypes.GasConfig{}, newCtx.TransientKVGasConfig()) - suite.Equal(storetypes.Gas(math.MaxUint64), newCtx.GasMeter().GasRemaining()) - suite.False(suite.app.EvmKeeper.IsSenderNonceIncreasedByAnteHandle(newCtx), "flag must be cleared") - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/app/ante/evm/setup_test.go b/app/ante/evm/setup_test.go deleted file mode 100644 index ba3e8eb13d..0000000000 --- a/app/ante/evm/setup_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package evm_test - -import ( - "testing" - "time" - - storetypes "cosmossdk.io/store/types" - - "github.com/EscanBE/evermint/v12/app/helpers" - "github.com/EscanBE/evermint/v12/constants" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/stretchr/testify/suite" - - sdkmath "cosmossdk.io/math" - chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/app/ante" - "github.com/EscanBE/evermint/v12/ethereum/eip712" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/ethereum/go-ethereum/core/types" -) - -type AnteTestSuite struct { - suite.Suite - - ctx sdk.Context - app *chainapp.Evermint - clientCtx client.Context - anteHandler sdk.AnteHandler - ethSigner types.Signer - enableFeemarket bool - evmParamsOption func(*evmtypes.Params) -} - -const TestGasLimit uint64 = 100000 - -func (suite *AnteTestSuite) SetupTest() { - checkTx := false - - suite.app = helpers.EthSetup(checkTx, func(chainApp *chainapp.Evermint, genesis chainapp.GenesisState) chainapp.GenesisState { - { - // setup x/feemarket genesis params - feemarketGenesis := feemarkettypes.DefaultGenesisState() - if !suite.enableFeemarket { - feemarketGenesis.Params.BaseFee = sdkmath.ZeroInt() - feemarketGenesis.Params.MinGasPrice = sdkmath.LegacyZeroDec() - } - err := feemarketGenesis.Validate() - suite.Require().NoError(err) - genesis[feemarkettypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(feemarketGenesis) - } - - { - // setup x/evm genesis params - evmGenesis := evmtypes.DefaultGenesisState() - if suite.evmParamsOption != nil { - suite.evmParamsOption(&evmGenesis.Params) - } - genesis[evmtypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(evmGenesis) - } - return genesis - }) - - chainId := constants.TestnetFullChainId - - suite.ctx = suite.app.BaseApp.NewContext(checkTx).WithBlockHeader(tmproto.Header{Height: 2, ChainID: chainId, Time: time.Now().UTC()}) - suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(evmtypes.DefaultEVMDenom, sdkmath.OneInt()))) - suite.ctx = suite.ctx.WithBlockGasMeter(storetypes.NewGasMeter(1000000000000000000)) - suite.ctx = suite.ctx.WithChainID(chainId) - suite.app.EvmKeeper.WithChainID(suite.ctx) - - // set staking denomination to Evermint denom - params, err := suite.app.StakingKeeper.GetParams(suite.ctx) - suite.Require().NoError(err) - params.BondDenom = constants.BaseDenom - err = suite.app.StakingKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - infCtx := suite.ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) - err = suite.app.AccountKeeper.Params.Set(infCtx, authtypes.DefaultParams()) - suite.Require().NoError(err) - - encodingConfig := chainapp.RegisterEncodingConfig() - // We're using TestMsg amino encoding in some tests, so register it here. - encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) - eip712.SetEncodingConfig(encodingConfig) - - suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) - - suite.Require().NotNil(suite.app.AppCodec()) - - anteHandler := ante.NewAnteHandler(ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - EvmKeeper: suite.app.EvmKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - }.WithDefaultDisabledAuthzMsgs()) - - suite.anteHandler = anteHandler - suite.ethSigner = types.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) -} - -func TestAnteTestSuite(t *testing.T) { - suite.Run(t, &AnteTestSuite{ - enableFeemarket: true, - }) -} diff --git a/app/ante/evm/signverify_test.go b/app/ante/evm/signverify_test.go deleted file mode 100644 index 246e6fe66d..0000000000 --- a/app/ante/evm/signverify_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package evm_test - -import ( - "math/big" - - ethante "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/testutil" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - ethtypes "github.com/ethereum/go-ethereum/core/types" -) - -func (suite *AnteTestSuite) TestEthSigVerificationDecorator() { - addr, privKey := testutiltx.NewAddrKey() - - ethContractCreationTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - } - signedTx := evmtypes.NewTx(ethContractCreationTxParams) - err := signedTx.Sign(suite.ethSigner, testutiltx.NewSigner(privKey)) - suite.Require().NoError(err) - - uprotectedEthTxParams := &evmtypes.EvmTxArgs{ - From: addr, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - } - unprotectedTx := evmtypes.NewTx(uprotectedEthTxParams) - err = unprotectedTx.Sign(ethtypes.HomesteadSigner{}, testutiltx.NewSigner(privKey)) - suite.Require().NoError(err) - - testCases := []struct { - name string - tx sdk.Tx - reCheckTx bool - expPass bool - expPanic bool - }{ - { - name: "fail - ReCheckTx", - tx: &testutiltx.InvalidTx{}, - reCheckTx: true, - expPass: false, - expPanic: true, - }, - { - name: "fail - invalid transaction type", - tx: &testutiltx.InvalidTx{}, - reCheckTx: false, - expPass: false, - expPanic: true, - }, - { - name: "fail - invalid sender", - tx: evmtypes.NewTx(&evmtypes.EvmTxArgs{ - To: &addr, - Nonce: 1, - Amount: big.NewInt(10), - GasLimit: 21000, - GasPrice: big.NewInt(1), - ChainID: suite.app.EvmKeeper.ChainID(), - }), - reCheckTx: false, - expPass: false, - }, - { - name: "pass - successful signature verification", - tx: signedTx, - reCheckTx: false, - expPass: true, - }, - { - name: "fail - invalid, reject unprotected txs", - tx: unprotectedTx, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - reject unprotected txs", - tx: unprotectedTx, - reCheckTx: false, - expPass: false, - }, - { - name: "fail - reject if sender is already set and doesn't match the signature", - tx: func() *evmtypes.MsgEthereumTx { - addr2, _ := testutiltx.NewAddrKey() - - copied := *signedTx - copied.From = sdk.AccAddress(addr2.Bytes()).String() - - return &copied - }(), - reCheckTx: false, - expPass: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.evmParamsOption = func(params *evmtypes.Params) {} - suite.SetupTest() - dec := ethante.NewEthSigVerificationDecorator(suite.app.EvmKeeper) - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, testutil.NextFn) - }) - return - } - - _, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, testutil.NextFn) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } - suite.evmParamsOption = nil -} diff --git a/app/ante/evm/sigs_test.go b/app/ante/evm/sigs_test.go deleted file mode 100644 index adcd577e64..0000000000 --- a/app/ante/evm/sigs_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package evm_test - -import ( - "math/big" - - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - "github.com/EscanBE/evermint/v12/x/evm/statedb" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" -) - -func (suite *AnteTestSuite) TestSignatures() { - suite.SetupTest() // reset - - addr, privKey := utiltx.NewAddrKey() - to := utiltx.GenerateAddress() - - acc := statedb.NewEmptyAccount() - acc.Nonce = 1 - acc.Balance = big.NewInt(10000000000) - - err := suite.app.EvmKeeper.SetAccount(suite.ctx, addr, *acc) - suite.Require().NoError(err) - ethTxParams := &evmtypes.EvmTxArgs{ - From: addr, - ChainID: suite.app.EvmKeeper.ChainID(), - Nonce: 1, - To: &to, - Amount: big.NewInt(10), - GasLimit: 100000, - GasPrice: big.NewInt(1), - } - msgEthereumTx := evmtypes.NewTx(ethTxParams) - - // CreateTestTx will sign the msgEthereumTx but not sign the cosmos tx since we have signCosmosTx as false - tx := suite.CreateTestTx(msgEthereumTx, privKey, 1, false, false) - sigs, err := tx.GetSignaturesV2() - suite.Require().NoError(err) - - // signatures of cosmos tx should be empty - suite.Require().Equal(len(sigs), 0) -} diff --git a/app/ante/evm/sigverify.go b/app/ante/evm/sigverify.go deleted file mode 100644 index d2ed807380..0000000000 --- a/app/ante/evm/sigverify.go +++ /dev/null @@ -1,68 +0,0 @@ -package evm - -import ( - "math/big" - - errorsmod "cosmossdk.io/errors" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - ethtypes "github.com/ethereum/go-ethereum/core/types" -) - -// EthSigVerificationDecorator validates an ethereum signatures -type EthSigVerificationDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator -func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator { - return EthSigVerificationDecorator{ - evmKeeper: ek, - } -} - -// AnteHandle validates checks that the registered chain id is the same as the one on the message, and -// that the signer address matches the one defined on the message. -// It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work. -// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user -// won't see the error message. -func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - chainID := esvd.evmKeeper.ChainID() - evmParams := esvd.evmKeeper.GetParams(ctx) - chainCfg := evmParams.GetChainConfig() - ethCfg := chainCfg.EthereumConfig(chainID) - blockNum := big.NewInt(ctx.BlockHeight()) - signer := ethtypes.MakeSigner(ethCfg, blockNum) - - { - msgEthTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - - ethTx := msgEthTx.AsTransaction() - if !ethTx.Protected() { - return ctx, errorsmod.Wrapf( - errortypes.ErrNotSupported, - "rejected unprotected Ethereum transaction. Please EIP155 sign your transaction to protect it against replay-attacks", - ) - } - - sender, err := signer.Sender(ethTx) - if err != nil { - return ctx, errorsmod.Wrapf( - errortypes.ErrorInvalidSigner, - "couldn't retrieve sender address from the ethereum transaction: %s", err.Error(), - ) - } - - senderBech32 := sdk.AccAddress(sender.Bytes()).String() - if msgEthTx.From != senderBech32 { - return ctx, errorsmod.Wrapf( - errortypes.ErrorInvalidSigner, - "mis-match sender address %s vs %s (%s) from signer", - msgEthTx.From, senderBech32, sender.Hex(), - ) - } - } - - return next(ctx, tx, simulate) -} diff --git a/app/ante/evm/utils_test.go b/app/ante/evm/utils_test.go deleted file mode 100644 index 985ecbd068..0000000000 --- a/app/ante/evm/utils_test.go +++ /dev/null @@ -1,613 +0,0 @@ -package evm_test - -import ( - "context" - "math/big" - "time" - - evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" - - sdkmath "cosmossdk.io/math" - - "github.com/EscanBE/evermint/v12/ethereum/eip712" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/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/EscanBE/evermint/v12/crypto/ethsecp256k1" - "github.com/cosmos/cosmos-sdk/client" - clienttx "github.com/cosmos/cosmos-sdk/client/tx" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/crypto/types/multisig" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - sdkante "github.com/cosmos/cosmos-sdk/x/auth/ante" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" - "github.com/cosmos/cosmos-sdk/x/authz" - ibctypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" - ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - - evtypes "cosmossdk.io/x/evidence/types" - "cosmossdk.io/x/feegrant" - utiltx "github.com/EscanBE/evermint/v12/testutil/tx" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" -) - -func (suite *AnteTestSuite) BuildTestEthTx( - from common.Address, - to common.Address, - amount *big.Int, - input []byte, - gasPrice *big.Int, - gasFeeCap *big.Int, - gasTipCap *big.Int, - accesses *ethtypes.AccessList, -) *evmtypes.MsgEthereumTx { - chainID := suite.app.EvmKeeper.ChainID() - nonce := suite.app.EvmKeeper.GetNonce( - suite.ctx, - common.BytesToAddress(from.Bytes()), - ) - - ethTxParams := &evmtypes.EvmTxArgs{ - From: from, - ChainID: chainID, - Nonce: nonce, - To: &to, - Amount: amount, - GasLimit: TestGasLimit, - GasPrice: gasPrice, - GasFeeCap: gasFeeCap, - GasTipCap: gasTipCap, - Input: input, - Accesses: accesses, - } - - msgEthereumTx := evmtypes.NewTx(ethTxParams) - return msgEthereumTx -} - -// CreateTestTx is a helper function to create a tx given multiple inputs. -// -//nolint:revive -func (suite *AnteTestSuite) CreateTestTx( - msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool, - unsetExtensionOptions bool, -) authsigning.Tx { - return suite.CreateTestTxBuilder(msg, priv, accNum, signCosmosTx, unsetExtensionOptions, false).GetTx() -} - -// CreateTestTxBuilder is a helper function to create a tx builder given multiple inputs. -func (suite *AnteTestSuite) CreateTestTxBuilder( - msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool, - unsetExtensionOptions, skipSign bool, -) client.TxBuilder { - var option *codectypes.Any - var err error - if !unsetExtensionOptions { - option, err = codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) - suite.Require().NoError(err) - } - - txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() - builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) - suite.Require().True(ok) - - signMode, err := authsigning.APISignModeToInternal(suite.clientCtx.TxConfig.SignModeHandler().DefaultMode()) - suite.Require().NoError(err) - - if !unsetExtensionOptions { - builder.SetExtensionOptions(option) - } - - if !skipSign { - err = msg.Sign(suite.ethSigner, utiltx.NewSigner(priv)) - suite.Require().NoError(err) - } - - err = builder.SetMsgs(msg) - suite.Require().NoError(err) - - ethTx := msg.AsTransaction() - - fees := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxFee(ethTx)))) - builder.SetFeeAmount(fees) - builder.SetGasLimit(ethTx.Gas()) - - if signCosmosTx { - // First round: we gather all the signer infos. We use the "set empty - // signature" hack to do that. - sigV2 := signing.SignatureV2{ - PubKey: priv.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: signMode, - Signature: nil, - }, - Sequence: ethTx.Nonce(), - } - - sigsV2 := []signing.SignatureV2{sigV2} - - err = txBuilder.SetSignatures(sigsV2...) - suite.Require().NoError(err) - - // Second round: all signer infos are set, so each signer can sign. - - signerData := authsigning.SignerData{ - ChainID: suite.ctx.ChainID(), - AccountNumber: accNum, - Sequence: ethTx.Nonce(), - } - sigV2, err = clienttx.SignWithPrivKey( - suite.ctx, - signMode, signerData, - txBuilder, priv, suite.clientCtx.TxConfig, ethTx.Nonce(), - ) - suite.Require().NoError(err) - - sigsV2 = []signing.SignatureV2{sigV2} - - err = txBuilder.SetSignatures(sigsV2...) - suite.Require().NoError(err) - } - - return txBuilder -} - -func (suite *AnteTestSuite) TxForTypedData(txBuilder client.TxBuilder) sdk.Tx { - return txBuilder.GetTx() -} - -func (suite *AnteTestSuite) CreateTestCosmosTxBuilder(gasPrice sdkmath.Int, denom string, msgs ...sdk.Msg) client.TxBuilder { - txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() - - txBuilder.SetGasLimit(TestGasLimit) - fees := &sdk.Coins{{Denom: denom, Amount: gasPrice.MulRaw(int64(TestGasLimit))}} - txBuilder.SetFeeAmount(*fees) - err := txBuilder.SetMsgs(msgs...) - suite.Require().NoError(err) - return txBuilder -} - -func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - // Build MsgSend - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgSend := banktypes.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgSend) -} - -func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - // Build MsgSend - valEthAddr := utiltx.GenerateAddress() - valAddr := sdk.ValAddress(valEthAddr.Bytes()) - msgSend := stakingtypes.NewMsgDelegate(from.String(), valAddr.String(), sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20))) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgSend) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - // Build MsgCreateValidator - valAddr := sdk.ValAddress(from.Bytes()) - privEd := ed25519.GenPrivKey() - msgCreate, err := stakingtypes.NewMsgCreateValidator( - valAddr.String(), - privEd.PubKey(), - sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)), - stakingtypes.NewDescription("moniker", "indentity", "website", "security_contract", "details"), - stakingtypes.NewCommissionRates(sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec()), - sdkmath.OneInt(), - ) - suite.Require().NoError(err) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgCreate) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator2(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - // Build MsgCreateValidator - valAddr := sdk.ValAddress(from.Bytes()) - privEd := ed25519.GenPrivKey() - msgCreate, err := stakingtypes.NewMsgCreateValidator( - valAddr.String(), - privEd.PubKey(), - sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)), - // Ensure optional fields can be left blank - stakingtypes.NewDescription("moniker", "indentity", "", "", ""), - stakingtypes.NewCommissionRates(sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec()), - sdkmath.OneInt(), - ) - suite.Require().NoError(err) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgCreate) -} - -func (suite *AnteTestSuite) CreateTestEIP712SubmitProposal(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins, deposit sdk.Coins) (client.TxBuilder, error) { - proposal, ok := govtypes.ContentFromProposalType("My proposal", "My description", govtypes.ProposalTypeText) - suite.Require().True(ok) - msgSubmit, err := govtypes.NewMsgSubmitProposal(proposal, deposit, from) - suite.Require().NoError(err) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgSubmit) -} - -func (suite *AnteTestSuite) CreateTestEIP712GrantAllowance(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - spendLimit := sdk.NewCoins(sdk.NewInt64Coin(evmtypes.DefaultEVMDenom, 10)) - threeHours := time.Now().Add(3 * time.Hour) - basic := &feegrant.BasicAllowance{ - SpendLimit: spendLimit, - Expiration: &threeHours, - } - granted := utiltx.GenerateAddress() - grantedAddr := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, granted.Bytes()) - msgGrant, err := feegrant.NewMsgGrantAllowance(basic, from, grantedAddr.GetAddress()) - suite.Require().NoError(err) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgGrant) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgEditValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - valAddr := sdk.ValAddress(from.Bytes()) - msgEdit := stakingtypes.NewMsgEditValidator( - valAddr.String(), - stakingtypes.NewDescription("moniker", "identity", "website", "security_contract", "details"), - nil, - nil, - ) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgEdit) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - pk := ed25519.GenPrivKey() - msgEvidence, err := evtypes.NewMsgSubmitEvidence(from, &evtypes.Equivocation{ - Height: 11, - Time: time.Now().UTC(), - Power: 100, - ConsensusAddress: pk.PubKey().Address().String(), - }) - suite.Require().NoError(err) - - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgEvidence) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgVoteV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - msgVote := govtypesv1.NewMsgVote(from, 1, govtypesv1.VoteOption_VOTE_OPTION_YES, "") - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgVote) -} - -func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - // Build V1 proposal messages. Must all be same-type, since EIP-712 - // does not support arrays of variable type. - authAcc := suite.app.GovKeeper.GetGovernanceAccount(suite.ctx) - - proposal1, ok := govtypes.ContentFromProposalType("My proposal 1", "My description 1", govtypes.ProposalTypeText) - suite.Require().True(ok) - content1, err := govtypesv1.NewLegacyContent( - proposal1, - sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), authAcc.GetAddress().Bytes()), - ) - suite.Require().NoError(err) - - proposal2, ok := govtypes.ContentFromProposalType("My proposal 2", "My description 2", govtypes.ProposalTypeText) - suite.Require().True(ok) - content2, err := govtypesv1.NewLegacyContent( - proposal2, - sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), authAcc.GetAddress().Bytes()), - ) - suite.Require().NoError(err) - - proposalMsgs := []sdk.Msg{ - content1, - content2, - } - - // Build V1 proposal - msgProposal, err := govtypesv1.NewMsgSubmitProposal( - proposalMsgs, - sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100))), - sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), from.Bytes()), - "Metadata", "title", "summary", - false, - ) - - suite.Require().NoError(err) - - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgProposal) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgExec(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgSend := banktypes.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) - msgExec := authz.NewMsgExec(from, []sdk.Msg{msgSend}) - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, &msgExec) -} - -func (suite *AnteTestSuite) CreateTestEIP712MultipleMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgSend := banktypes.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) - return suite.CreateTestEIP712CosmosTxBuilder(priv, chainID, gas, gasAmount, []sdk.Msg{msgSend, msgSend, msgSend}) -} - -func (suite *AnteTestSuite) CreateTestEIP712MultipleDifferentMsgs(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgSend := banktypes.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) - - msgVote := govtypesv1.NewMsgVote(from, 1, govtypesv1.VoteOption_VOTE_OPTION_YES, "") - - valEthAddr := utiltx.GenerateAddress() - valAddr := sdk.ValAddress(valEthAddr.Bytes()) - msgDelegate := stakingtypes.NewMsgDelegate(from.String(), valAddr.String(), sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20))) - - return suite.CreateTestEIP712CosmosTxBuilder(priv, chainID, gas, gasAmount, []sdk.Msg{msgSend, msgVote, msgDelegate}) -} - -func (suite *AnteTestSuite) CreateTestEIP712SameMsgDifferentSchemas(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - msgVote1 := govtypesv1.NewMsgVote(from, 1, govtypesv1.VoteOption_VOTE_OPTION_YES, "") - msgVote2 := govtypesv1.NewMsgVote(from, 5, govtypesv1.VoteOption_VOTE_OPTION_ABSTAIN, "With Metadata") - - return suite.CreateTestEIP712CosmosTxBuilder(priv, chainID, gas, gasAmount, []sdk.Msg{msgVote1, msgVote2}) -} - -func (suite *AnteTestSuite) CreateTestEIP712ZeroValueArray(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgSend := banktypes.NewMsgSend(from, recipient, sdk.NewCoins()) - return suite.CreateTestEIP712CosmosTxBuilder(priv, chainID, gas, gasAmount, []sdk.Msg{msgSend}) -} - -func (suite *AnteTestSuite) CreateTestEIP712ZeroValueNumber(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - msgVote := govtypesv1.NewMsgVote(from, 0, govtypesv1.VoteOption_VOTE_OPTION_NO, "") - - return suite.CreateTestEIP712CosmosTxBuilder(priv, chainID, gas, gasAmount, []sdk.Msg{msgVote}) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgTransfer(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - msgTransfer := suite.createMsgTransfer(from, "With Memo") - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgTransfer) -} - -func (suite *AnteTestSuite) CreateTestEIP712MsgTransferWithoutMemo(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - msgTransfer := suite.createMsgTransfer(from, "") - return suite.CreateTestEIP712SingleMessageTxBuilder(priv, chainID, gas, gasAmount, msgTransfer) -} - -func (suite *AnteTestSuite) createMsgTransfer(from sdk.AccAddress, memo string) *ibctypes.MsgTransfer { - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgTransfer := ibctypes.NewMsgTransfer("transfer", "channel-25", sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100000)), from.String(), recipient.String(), ibcclienttypes.NewHeight(1000, 1000), 1000, memo) - return msgTransfer -} - -func (suite *AnteTestSuite) CreateTestEIP712MultipleSignerMsgs(from sdk.AccAddress, priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins) (client.TxBuilder, error) { - recipient := sdk.AccAddress(common.Address{}.Bytes()) - msgSend1 := banktypes.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) - msgSend2 := banktypes.NewMsgSend(recipient, from, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1)))) - return suite.CreateTestEIP712CosmosTxBuilder(priv, chainID, gas, gasAmount, []sdk.Msg{msgSend1, msgSend2}) -} - -func (suite *AnteTestSuite) CreateTestEIP712SingleMessageTxBuilder( - priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg, -) (client.TxBuilder, error) { - msgs := []sdk.Msg{msg} - return suite.CreateTestEIP712CosmosTxBuilder( - priv, - chainID, - gas, - gasAmount, - msgs, - ) -} - -func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder( - priv cryptotypes.PrivKey, chainID string, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg, -) (client.TxBuilder, error) { - txConf := suite.clientCtx.TxConfig - cosmosTxArgs := utiltx.CosmosTxArgs{ - TxCfg: txConf, - Priv: priv, - ChainID: chainID, - Gas: gas, - Fees: gasAmount, - Msgs: msgs, - } - - return utiltx.PrepareEIP712CosmosTx( - suite.ctx, - suite.app, - utiltx.EIP712TxArgs{ - CosmosTxArgs: cosmosTxArgs, - }, - ) -} - -// Generate a set of pub/priv keys to be used in creating multi-keys -func (suite *AnteTestSuite) GenerateMultipleKeys(n int) ([]cryptotypes.PrivKey, []cryptotypes.PubKey) { - privKeys := make([]cryptotypes.PrivKey, n) - pubKeys := make([]cryptotypes.PubKey, n) - for i := 0; i < n; i++ { - privKey, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) - privKeys[i] = privKey - pubKeys[i] = privKey.PubKey() - } - return privKeys, pubKeys -} - -// generateSingleSignature signs the given sign doc bytes using the given signType (EIP-712 or Standard) -func (suite *AnteTestSuite) generateSingleSignature(signMode signing.SignMode, privKey cryptotypes.PrivKey, signDocBytes []byte, signType string) (signature signing.SignatureV2) { - var ( - msg []byte - err error - ) - - msg = signDocBytes - - if signType == "EIP-712" { - msg, err = eip712.GetEIP712BytesForMsg(signDocBytes) - suite.Require().NoError(err) - } - - sigBytes, _ := privKey.Sign(msg) - sigData := &signing.SingleSignatureData{ - SignMode: signMode, - Signature: sigBytes, - } - - return signing.SignatureV2{ - PubKey: privKey.PubKey(), - Data: sigData, - } -} - -// generateMultikeySignatures signs a set of messages using each private key within a given multi-key -func (suite *AnteTestSuite) generateMultikeySignatures(signMode signing.SignMode, privKeys []cryptotypes.PrivKey, signDocBytes []byte, signType string) (signatures []signing.SignatureV2) { - n := len(privKeys) - signatures = make([]signing.SignatureV2, n) - - for i := 0; i < n; i++ { - privKey := privKeys[i] - currentType := signType - - // If mixed type, alternate signing type on each iteration - if signType == "mixed" { - if i%2 == 0 { - currentType = "EIP-712" - } else { - currentType = "Standard" - } - } - - signatures[i] = suite.generateSingleSignature( - signMode, - privKey, - signDocBytes, - currentType, - ) - } - - return signatures -} - -// RegisterAccount creates an account with the keeper and populates the initial balance -func (suite *AnteTestSuite) RegisterAccount(pubKey cryptotypes.PubKey, balance *big.Int) { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, sdk.AccAddress(pubKey.Address())) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - - err := suite.app.EvmKeeper.SetBalance(suite.ctx, common.BytesToAddress(pubKey.Address()), balance) - suite.Require().NoError(err) -} - -// createSignerBytes generates sign doc bytes using the given parameters -func (suite *AnteTestSuite) createSignerBytes(chainID string, signMode signing.SignMode, pubKey cryptotypes.PubKey, txBuilder client.TxBuilder) []byte { - acc, err := sdkante.GetSignerAcc(suite.ctx, suite.app.AccountKeeper, sdk.AccAddress(pubKey.Address())) - suite.Require().NoError(err) - signerInfo := authsigning.SignerData{ - Address: sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), acc.GetAddress().Bytes()), - ChainID: chainID, - AccountNumber: acc.GetAccountNumber(), - Sequence: acc.GetSequence(), - PubKey: pubKey, - } - - signerBytes, err := authsigning.GetSignBytesAdapter( - context.Background(), - suite.clientCtx.TxConfig.SignModeHandler(), - signMode, - signerInfo, - txBuilder.GetTx(), - ) - suite.Require().NoError(err) - - return signerBytes -} - -// createBaseTxBuilder creates a TxBuilder to be used for Single- or Multi-signing -func (suite *AnteTestSuite) createBaseTxBuilder(msg sdk.Msg, gas uint64) client.TxBuilder { - txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() - - txBuilder.SetGasLimit(gas) - txBuilder.SetFeeAmount(sdk.NewCoins( - sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(10000)), - )) - - err := txBuilder.SetMsgs(msg) - suite.Require().NoError(err) - - txBuilder.SetMemo("") - - return txBuilder -} - -// CreateTestSignedMultisigTx creates and sign a multi-signed tx for the given message. `signType` indicates whether to use standard signing ("Standard"), -// EIP-712 signing ("EIP-712"), or a mix of the two ("mixed"). -func (suite *AnteTestSuite) CreateTestSignedMultisigTx(privKeys []cryptotypes.PrivKey, signMode signing.SignMode, msg sdk.Msg, chainID string, gas uint64, signType string) client.TxBuilder { - pubKeys := make([]cryptotypes.PubKey, len(privKeys)) - for i, privKey := range privKeys { - pubKeys[i] = privKey.PubKey() - } - - // Re-derive multikey - numKeys := len(privKeys) - multiKey := kmultisig.NewLegacyAminoPubKey(numKeys, pubKeys) - - suite.RegisterAccount(multiKey, big.NewInt(10000000000)) - - txBuilder := suite.createBaseTxBuilder(msg, gas) - - // Prepare signature field - sig := multisig.NewMultisig(len(pubKeys)) - err := txBuilder.SetSignatures(signing.SignatureV2{ - PubKey: multiKey, - Data: sig, - }) - suite.Require().NoError(err) - - signerBytes := suite.createSignerBytes(chainID, signMode, multiKey, txBuilder) - - // Sign for each key and update signature field - sigs := suite.generateMultikeySignatures(signMode, privKeys, signerBytes, signType) - for _, pkSig := range sigs { - err := multisig.AddSignatureV2(sig, pkSig, pubKeys) - suite.Require().NoError(err) - } - - err = txBuilder.SetSignatures(signing.SignatureV2{ - PubKey: multiKey, - Data: sig, - }) - suite.Require().NoError(err) - - return txBuilder -} - -func (suite *AnteTestSuite) CreateTestSingleSignedTx(privKey cryptotypes.PrivKey, signMode signing.SignMode, msg sdk.Msg, chainID string, gas uint64, signType string) client.TxBuilder { - pubKey := privKey.PubKey() - - suite.RegisterAccount(pubKey, big.NewInt(10000000000)) - - txBuilder := suite.createBaseTxBuilder(msg, gas) - - // Prepare signature field - sig := signing.SingleSignatureData{} - err := txBuilder.SetSignatures(signing.SignatureV2{ - PubKey: pubKey, - Data: &sig, - }) - suite.Require().NoError(err) - - signerBytes := suite.createSignerBytes(chainID, signMode, pubKey, txBuilder) - - sigData := suite.generateSingleSignature(signMode, privKey, signerBytes, signType) - err = txBuilder.SetSignatures(sigData) - suite.Require().NoError(err) - - return txBuilder -} - -// zeroBaseFeeAndMinGasPrice updates x/feemarket params to disable base fee -func (suite *AnteTestSuite) zeroBaseFeeAndMinGasPrice(ctx sdk.Context) { - params := suite.app.FeeMarketKeeper.GetParams(ctx) - params.BaseFee = sdkmath.ZeroInt() - params.MinGasPrice = sdkmath.LegacyZeroDec() - err := suite.app.FeeMarketKeeper.SetParams(ctx, params) - suite.Require().NoError(err) -} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go deleted file mode 100644 index 0c1aa0af56..0000000000 --- a/app/ante/handler_options.go +++ /dev/null @@ -1,151 +0,0 @@ -package ante - -import ( - errorsmod "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" - txsigning "cosmossdk.io/x/tx/signing" - anteutils "github.com/EscanBE/evermint/v12/app/ante/utils" - vauthtypes "github.com/EscanBE/evermint/v12/x/vauth/types" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - sdkauthante "github.com/cosmos/cosmos-sdk/x/auth/ante" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante" - ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" - - cosmosante "github.com/EscanBE/evermint/v12/app/ante/cosmos" - evmante "github.com/EscanBE/evermint/v12/app/ante/evm" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - - sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" -) - -// HandlerOptions defines the list of module keepers required to run this chain -// AnteHandler decorators. -type HandlerOptions struct { - Cdc codec.BinaryCodec - AccountKeeper *authkeeper.AccountKeeper - BankKeeper bankkeeper.Keeper - DistributionKeeper *distrkeeper.Keeper - IBCKeeper *ibckeeper.Keeper - StakingKeeper *stakingkeeper.Keeper - FeeMarketKeeper evmante.FeeMarketKeeper - EvmKeeper evmante.EVMKeeper - VAuthKeeper cosmosante.VAuthKeeper - FeegrantKeeper sdkauthante.FeegrantKeeper - ExtensionOptionChecker sdkauthante.ExtensionOptionChecker - SignModeHandler *txsigning.HandlerMap - SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error - TxFeeChecker anteutils.TxFeeChecker - DisabledAuthzMsgs map[string]bool -} - -func (options HandlerOptions) WithDefaultDisabledAuthzMsgs() HandlerOptions { - options.DisabledAuthzMsgs = map[string]bool{ - sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}): true, - sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}): true, - sdk.MsgTypeURL(&sdkvesting.MsgCreatePeriodicVestingAccount{}): true, - sdk.MsgTypeURL(&sdkvesting.MsgCreatePermanentLockedAccount{}): true, - } - - return options -} - -// Validate checks if the keepers are defined -func (options HandlerOptions) Validate() error { - if options.Cdc == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "codec is required for AnteHandler") - } - if options.AccountKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "account keeper is required for AnteHandler") - } - if options.BankKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "bank keeper is required for AnteHandler") - } - if options.IBCKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "ibc keeper is required for AnteHandler") - } - if options.StakingKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "staking keeper is required for AnteHandler") - } - if options.FeeMarketKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "fee market keeper is required for AnteHandler") - } - if options.EvmKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler") - } - if options.VAuthKeeper == nil { - return errorsmod.Wrapf(errortypes.ErrLogic, "%s keeper is required for AnteHandler", vauthtypes.ModuleName) - } - if options.SigGasConsumer == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "signature gas consumer is required for AnteHandler") - } - if options.SignModeHandler == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "sign mode handler is required for AnteHandler") - } - if options.DistributionKeeper == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "distribution keeper is required for AnteHandler") - } - if options.TxFeeChecker == nil { - return errorsmod.Wrap(errortypes.ErrLogic, "tx fee checker is required for AnteHandler") - } - if len(options.DisabledAuthzMsgs) < 1 { - return errorsmod.Wrap(errortypes.ErrLogic, "disabled authz msgs is required for AnteHandler") - } - return nil -} - -// newEVMAnteHandler creates the default AnteHandler for Ethereum transactions -func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler { - return sdk.ChainAnteDecorators( - // outermost AnteDecorator. SetUpContext must be called first - evmante.NewEthSetUpContextDecorator(options.EvmKeeper), - // ensure one and only one evm tx per tx - evmante.NewSingleEthTxDecorator(), - // Check eth effective gas price against the node's minimal-gas-prices config - evmante.NewEthMempoolFeeDecorator(options.EvmKeeper), - // Check eth effective gas price against the global MinGasPrice - evmante.NewEthMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), - evmante.NewEthValidateBasicDecorator(options.EvmKeeper), - evmante.NewEthSigVerificationDecorator(options.EvmKeeper), - evmante.NewExternalOwnedAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper), - evmante.NewCanTransferDecorator(options.EvmKeeper), - evmante.NewEthGasConsumeDecorator(options.BankKeeper, *options.DistributionKeeper, options.EvmKeeper, *options.StakingKeeper), - evmante.NewEthIncrementSenderSequenceDecorator(options.AccountKeeper, options.EvmKeeper), - evmante.NewEthSetupExecutionDecorator(options.EvmKeeper), - // emit eth tx hash and index at the very last ante handler. - evmante.NewEthEmitEventDecorator(options.EvmKeeper), - ) -} - -// newCosmosAnteHandler creates the default AnteHandler for Cosmos transactions -func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler { - return sdk.ChainAnteDecorators( - cosmosante.RejectMessagesDecorator{}, // reject MsgEthereumTxs - cosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field - options.DisabledAuthzMsgs, - ), - sdkauthante.NewSetUpContextDecorator(), - sdkauthante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), - sdkauthante.NewValidateBasicDecorator(), - cosmosante.NewVestingMessagesAuthorizationDecorator(options.VAuthKeeper), - sdkauthante.NewTxTimeoutHeightDecorator(), - sdkauthante.NewValidateMemoDecorator(options.AccountKeeper), - cosmosante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper), - sdkauthante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), - cosmosante.NewDeductFeeDecorator(*options.AccountKeeper, options.BankKeeper, *options.DistributionKeeper, options.FeegrantKeeper, *options.StakingKeeper, options.TxFeeChecker), - // SetPubKeyDecorator must be called before all signature verification decorators - sdkauthante.NewSetPubKeyDecorator(options.AccountKeeper), - sdkauthante.NewValidateSigCountDecorator(options.AccountKeeper), - sdkauthante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), - sdkauthante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), - sdkauthante.NewIncrementSequenceDecorator(options.AccountKeeper), - ibcante.NewRedundantRelayDecorator(options.IBCKeeper), - ) -} diff --git a/app/ante/handler_options_test.go b/app/ante/handler_options_test.go deleted file mode 100644 index dc716cdc02..0000000000 --- a/app/ante/handler_options_test.go +++ /dev/null @@ -1,229 +0,0 @@ -package ante_test - -import ( - ethante "github.com/EscanBE/evermint/v12/app/ante/evm" - evertypes "github.com/EscanBE/evermint/v12/types" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/app/ante" -) - -func (suite *AnteTestSuite) TestDefaultDisabledAuthzMsgs() { - optionsWithdDefaultDisabledAuthzMsgs := ante.HandlerOptions{}.WithDefaultDisabledAuthzMsgs() - suite.Require().NotEmpty(optionsWithdDefaultDisabledAuthzMsgs.DisabledAuthzMsgs) - _, foundMsgEthereum := optionsWithdDefaultDisabledAuthzMsgs.DisabledAuthzMsgs[sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{})] - suite.Require().True(foundMsgEthereum, "MsgEthereumTx should be disabled by default") -} - -func (suite *AnteTestSuite) TestValidateHandlerOptions() { - encodingConfig := chainapp.RegisterEncodingConfig() - - cases := []struct { - name string - options ante.HandlerOptions - expPass bool - }{ - { - name: "fail - empty options", - options: ante.HandlerOptions{}, - expPass: false, - }, - { - name: "fail - empty account keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty bank keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty distribution keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: nil, - - IBCKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty IBC keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - - IBCKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty staking keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty fee market keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty EVM keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - EvmKeeper: nil, - }, - expPass: false, - }, - { - name: "fail - empty VAuth keeper", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - EvmKeeper: suite.app.EvmKeeper, - VAuthKeeper: &suite.app.VAuthKeeper, - }, - expPass: false, - }, - { - name: "fail - empty signature gas consumer", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - EvmKeeper: suite.app.EvmKeeper, - SigGasConsumer: nil, - }, - expPass: false, - }, - { - name: "fail - empty signature mode handler", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - EvmKeeper: suite.app.EvmKeeper, - SigGasConsumer: ante.SigVerificationGasConsumer, - SignModeHandler: nil, - }, - expPass: false, - }, - { - name: "fail - empty tx fee checker", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - EvmKeeper: suite.app.EvmKeeper, - SigGasConsumer: ante.SigVerificationGasConsumer, - SignModeHandler: suite.app.GetTxConfig().SignModeHandler(), - TxFeeChecker: nil, - }, - expPass: false, - }, - { - name: "fail - empty disabled authz msgs", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - ExtensionOptionChecker: evertypes.HasDynamicFeeExtensionOption, - EvmKeeper: suite.app.EvmKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - IBCKeeper: suite.app.IBCKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - TxFeeChecker: ethante.NewDynamicFeeChecker(suite.app.EvmKeeper), - }, - expPass: false, - }, - { - name: "pass - default app options", - options: ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - ExtensionOptionChecker: evertypes.HasDynamicFeeExtensionOption, - EvmKeeper: suite.app.EvmKeeper, - VAuthKeeper: &suite.app.VAuthKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - IBCKeeper: suite.app.IBCKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - TxFeeChecker: ethante.NewDynamicFeeChecker(suite.app.EvmKeeper), - }.WithDefaultDisabledAuthzMsgs(), - expPass: true, - }, - } - - for _, tc := range cases { - suite.Run(tc.name, func() { - err := tc.options.Validate() - if tc.expPass { - suite.Require().NoError(err, tc.name) - } else { - suite.Require().Error(err, tc.name) - } - }) - } -} diff --git a/app/ante/integration_test.go b/app/ante/integration_test.go deleted file mode 100644 index b0e4e9979c..0000000000 --- a/app/ante/integration_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package ante_test - -import ( - "time" - - sdkmath "cosmossdk.io/math" - "github.com/EscanBE/evermint/v12/constants" - "github.com/EscanBE/evermint/v12/rename_chain/marker" - testutiltx "github.com/EscanBE/evermint/v12/testutil/tx" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" - "github.com/EscanBE/evermint/v12/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var _ = Describe("when sending a Cosmos transaction", func() { - var ( - addr sdk.AccAddress - priv *ethsecp256k1.PrivKey - msg sdk.Msg - ) - - Context("and the sender account has enough balance to pay for the transaction cost", Ordered, func() { - balance := sdkmath.NewInt(1e18) - - BeforeEach(func() { - addr, priv = testutiltx.NewAccAddressAndKey() - - msg = &banktypes.MsgSend{ - FromAddress: addr.String(), - ToAddress: marker.ReplaceAbleAddress("evm1dx67l23hz9l0k9hcher8xz04uj7wf3yuqpfj0p"), - Amount: sdk.Coins{sdk.Coin{Amount: sdkmath.NewInt(1e14), Denom: constants.BaseDenom}}, - } - - err := testutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, addr, balance.Int64()) - Expect(err).To(BeNil()) - - s.ctx, err = testutil.Commit(s.ctx, s.app, time.Second*0, nil) - Expect(err).To(BeNil()) - }) - - It("should succeed", func() { - ctx, res, err := testutil.DeliverTx(s.ctx, s.app, priv, nil, msg) - Expect(err).To(BeNil()) - Expect(res.IsOK()).To(BeTrue()) - s.ctx = ctx - }) - }) - - Context("and the sender account has NOT enough balance to pay for the transaction cost", Ordered, func() { - BeforeEach(func() { - addr, priv = testutiltx.NewAccAddressAndKey() - - msg = &banktypes.MsgSend{ - FromAddress: addr.String(), - ToAddress: marker.ReplaceAbleAddress("evm1dx67l23hz9l0k9hcher8xz04uj7wf3yuqpfj0p"), - Amount: sdk.Coins{sdk.Coin{Amount: sdkmath.NewInt(1e14), Denom: constants.BaseDenom}}, - } - }) - - It("should fail", func() { - _, res, err := testutil.DeliverTx(s.ctx, s.app, priv, nil, msg) - Expect(res.IsOK()).To(BeTrue()) - Expect(err).To(HaveOccurred()) - }) - }) -}) diff --git a/app/ante/setup_test.go b/app/ante/setup_test.go deleted file mode 100644 index a212e37d10..0000000000 --- a/app/ante/setup_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package ante_test - -import ( - "testing" - "time" - - "github.com/EscanBE/evermint/v12/app/helpers" - "github.com/EscanBE/evermint/v12/constants" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" - "github.com/EscanBE/evermint/v12/testutil" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - "github.com/cosmos/cosmos-sdk/client" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -var s *AnteTestSuite - -type AnteTestSuite struct { - suite.Suite - - ctx sdk.Context - clientCtx client.Context - app *chainapp.Evermint - denom string -} - -func (suite *AnteTestSuite) SetupTest() { - t := suite.T() - privCons, err := ethsecp256k1.GenerateKey() - require.NoError(t, err) - consAddress := sdk.ConsAddress(privCons.PubKey().Address()) - - isCheckTx := false - chainID := constants.TestnetFullChainId - suite.app = helpers.Setup(isCheckTx, feemarkettypes.DefaultGenesisState(), chainID) - suite.Require().NotNil(suite.app.AppCodec()) - - header := testutil.NewHeader( - 1, time.Now().UTC(), chainID, consAddress, nil, nil) - suite.ctx = suite.app.BaseApp.NewContext(isCheckTx).WithBlockHeader(header) - suite.ctx = suite.ctx.WithChainID(chainID) - - suite.denom = constants.BaseDenom - evmParams := suite.app.EvmKeeper.GetParams(suite.ctx) - evmParams.EvmDenom = suite.denom - _ = suite.app.EvmKeeper.SetParams(suite.ctx, evmParams) - - encodingConfig := chainapp.RegisterEncodingConfig() - suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) -} - -func TestAnteTestSuite(t *testing.T) { - s = new(AnteTestSuite) - suite.Run(t, s) - - RegisterFailHandler(Fail) - RunSpecs(t, "Run AnteHandler Integration Tests") -} diff --git a/app/ante/sigverify.go b/app/ante/sigverify.go deleted file mode 100644 index 7ffcf81e9e..0000000000 --- a/app/ante/sigverify.go +++ /dev/null @@ -1,88 +0,0 @@ -package ante - -import ( - "fmt" - - storetypes "cosmossdk.io/store/types" - - errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/crypto/types/multisig" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" -) - -var _ authante.SignatureVerificationGasConsumer = SigVerificationGasConsumer - -const ( - Secp256k1VerifyCost uint64 = 21000 -) - -// SigVerificationGasConsumer is this chain's implementation of SignatureVerificationGasConsumer. It consumes gas -// for signature verification based upon the public key type. The cost is fetched from the given params and is matched -// by the concrete type. -// The types of keys supported are: -// -// - ethsecp256k1 (Ethereum keys) -// -// - ed25519 (Validators) -// -// - multisig (Cosmos SDK multisigs) -func SigVerificationGasConsumer( - meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params, -) error { - pubkey := sig.PubKey - switch pubkey := pubkey.(type) { - - case *ethsecp256k1.PubKey: - // Ethereum keys - meter.ConsumeGas(Secp256k1VerifyCost, "ante verify: eth_secp256k1") - return nil - case *ed25519.PubKey: - // Validator keys - meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") - return errorsmod.Wrap(errortypes.ErrInvalidPubKey, "ED25519 public keys are unsupported") - - case multisig.PubKey: - // Multisig keys - multisignature, ok := sig.Data.(*signing.MultiSignatureData) - if !ok { - return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data) - } - return ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence) - - default: - return errorsmod.Wrapf(errortypes.ErrInvalidPubKey, "unrecognized/unsupported public key type: %T", pubkey) - } -} - -// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature -func ConsumeMultisignatureVerificationGas( - meter storetypes.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey, - params authtypes.Params, accSeq uint64, -) error { - size := sig.BitArray.Count() - sigIndex := 0 - - for i := 0; i < size; i++ { - if !sig.BitArray.GetIndex(i) { - continue - } - sigV2 := signing.SignatureV2{ - PubKey: pubkey.GetPubKeys()[i], - Data: sig.Signatures[sigIndex], - Sequence: accSeq, - } - err := SigVerificationGasConsumer(meter, sigV2, params) - if err != nil { - return err - } - sigIndex++ - } - - return nil -} diff --git a/app/ante/sigverify_test.go b/app/ante/sigverify_test.go deleted file mode 100644 index 8d151f23ce..0000000000 --- a/app/ante/sigverify_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package ante_test - -import ( - "testing" - - storetypes "cosmossdk.io/store/types" - - chainapp "github.com/EscanBE/evermint/v12/app" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/crypto/types/multisig" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - "github.com/EscanBE/evermint/v12/app/ante" - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" -) - -func TestConsumeSignatureVerificationGas(t *testing.T) { - params := authtypes.DefaultParams() - msg := []byte{1, 2, 3, 4} - - encodingConfig := chainapp.RegisterEncodingConfig() - cdc := encodingConfig.Amino - - p := authtypes.DefaultParams() - pkSet1, sigSet1 := generatePubKeysAndSignatures(5, msg, false) - multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, pkSet1) - multisignature1 := multisig.NewMultisig(len(pkSet1)) - expectedCost1 := expectedGasCostByKeys(pkSet1) - - for i := 0; i < len(pkSet1); i++ { - stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]} //nolint:staticcheck - sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig) - require.NoError(t, err) - err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1) - require.NoError(t, err) - } - - ethsecKey, _ := ethsecp256k1.GenerateKey() - skR1, _ := secp256r1.GenPrivKey() - - type args struct { - meter storetypes.GasMeter - sig signing.SignatureData - pubkey cryptotypes.PubKey - params authtypes.Params - } - tests := []struct { - name string - args args - gasConsumed uint64 - shouldErr bool - }{ - { - name: "fail - PubKeyEd25519", - args: args{storetypes.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, - gasConsumed: p.SigVerifyCostED25519, - shouldErr: true, - }, - { - name: "pass - PubKeyEthsecp256k1", - args: args{storetypes.NewInfiniteGasMeter(), nil, ethsecKey.PubKey(), params}, - gasConsumed: ante.Secp256k1VerifyCost, - shouldErr: false, - }, - { - name: "fail - PubKeySecp256k1", - args: args{storetypes.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, - gasConsumed: p.SigVerifyCostSecp256k1, - shouldErr: true, - }, - { - name: "fail - PubKeySecp256r1", - args: args{storetypes.NewInfiniteGasMeter(), nil, skR1.PubKey(), params}, - gasConsumed: p.SigVerifyCostSecp256r1(), - shouldErr: true, - }, - { - name: "pass - Multisig", - args: args{storetypes.NewInfiniteGasMeter(), multisignature1, multisigKey1, params}, - gasConsumed: expectedCost1, - shouldErr: false, - }, - { - name: "fail - unknown key", - args: args{storetypes.NewInfiniteGasMeter(), nil, nil, params}, - gasConsumed: 0, - shouldErr: true, - }, - } - for _, tt := range tests { - sigV2 := signing.SignatureV2{ - PubKey: tt.args.pubkey, - Data: tt.args.sig, - Sequence: 0, // Arbitrary account sequence - } - err := ante.SigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) - - if tt.shouldErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed()) - } - } -} diff --git a/app/ante/utils/fee_checker.go b/app/ante/utils/fee_checker.go deleted file mode 100644 index 2697759049..0000000000 --- a/app/ante/utils/fee_checker.go +++ /dev/null @@ -1,7 +0,0 @@ -package utils - -import sdk "github.com/cosmos/cosmos-sdk/types" - -// TxFeeChecker check if the provided fee is enough and returns the effective fee and tx priority, -// the effective fee should be deducted later, and the priority should be returned in abci response. -type TxFeeChecker func(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, int64, error) diff --git a/app/ante/utils/interfaces.go b/app/ante/utils/interfaces.go deleted file mode 100644 index 0994fc7146..0000000000 --- a/app/ante/utils/interfaces.go +++ /dev/null @@ -1,25 +0,0 @@ -package utils - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -// BankKeeper defines the exposed interface for using functionality of the bank keeper -// in the context of the AnteHandler utils package. -type BankKeeper interface { - GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin -} - -// DistributionKeeper defines the exposed interface for using functionality of the distribution -// keeper in the context of the AnteHandler utils package. -type DistributionKeeper interface { - WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) -} - -// StakingKeeper defines the exposed interface for using functionality of the staking keeper -// in the context of the AnteHandler utils package. -type StakingKeeper interface { - BondDenom(ctx sdk.Context) string - IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, fn func(index int64, delegation stakingtypes.DelegationI) (stop bool)) -} diff --git a/app/ante/utils/setup_test.go b/app/ante/utils/setup_test.go deleted file mode 100644 index 9ce047d143..0000000000 --- a/app/ante/utils/setup_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package utils_test - -import ( - "testing" - "time" - - storetypes "cosmossdk.io/store/types" - - "github.com/EscanBE/evermint/v12/app/helpers" - "github.com/EscanBE/evermint/v12/constants" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/stretchr/testify/suite" - - sdkmath "cosmossdk.io/math" - chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/app/ante" - "github.com/EscanBE/evermint/v12/ethereum/eip712" - "github.com/EscanBE/evermint/v12/testutil" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/ethereum/go-ethereum/core/types" -) - -type AnteTestSuite struct { - suite.Suite - - ctx sdk.Context - app *chainapp.Evermint - clientCtx client.Context - anteHandler sdk.AnteHandler - ethSigner types.Signer - enableFeemarket bool - evmParamsOption func(*evmtypes.Params) -} - -func (suite *AnteTestSuite) SetupTest() { - checkTx := false - - suite.app = helpers.EthSetup(checkTx, func(chainApp *chainapp.Evermint, genesis chainapp.GenesisState) chainapp.GenesisState { - { - // setup x/feemarket genesis params - feemarketGenesis := feemarkettypes.DefaultGenesisState() - if !suite.enableFeemarket { - feemarketGenesis.Params.BaseFee = sdkmath.ZeroInt() - feemarketGenesis.Params.MinGasPrice = sdkmath.LegacyZeroDec() - } - err := feemarketGenesis.Validate() - suite.Require().NoError(err) - genesis[feemarkettypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(feemarketGenesis) - } - - { - // setup x/evm genesis params - evmGenesis := evmtypes.DefaultGenesisState() - if suite.evmParamsOption != nil { - suite.evmParamsOption(&evmGenesis.Params) - } - genesis[evmtypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(evmGenesis) - } - return genesis - }) - - suite.ctx = suite.app.BaseApp.NewContext(checkTx).WithBlockHeader(tmproto.Header{Height: 1, ChainID: constants.TestnetFullChainId, Time: time.Now().UTC()}) - suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(evmtypes.DefaultEVMDenom, sdkmath.OneInt()))) - suite.ctx = suite.ctx.WithBlockGasMeter(storetypes.NewGasMeter(1000000000000000000)) - suite.app.EvmKeeper.WithChainID(suite.ctx) - - // set staking denomination to Evermint denom - params, err := suite.app.StakingKeeper.GetParams(suite.ctx) - suite.Require().NoError(err) - params.BondDenom = constants.BaseDenom - err = suite.app.StakingKeeper.SetParams(suite.ctx, params) - suite.Require().NoError(err) - - infCtx := suite.ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) - err = suite.app.AccountKeeper.Params.Set(infCtx, authtypes.DefaultParams()) - suite.Require().NoError(err) - - encodingConfig := chainapp.RegisterEncodingConfig() - // We're using TestMsg amino encoding in some tests, so register it here. - encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) - eip712.SetEncodingConfig(encodingConfig) - - suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) - - suite.Require().NotNil(suite.app.AppCodec()) - - anteHandler := ante.NewAnteHandler(ante.HandlerOptions{ - Cdc: suite.app.AppCodec(), - AccountKeeper: &suite.app.AccountKeeper, - BankKeeper: suite.app.BankKeeper, - DistributionKeeper: &suite.app.DistrKeeper, - EvmKeeper: suite.app.EvmKeeper, - FeegrantKeeper: suite.app.FeeGrantKeeper, - IBCKeeper: suite.app.IBCKeeper, - StakingKeeper: suite.app.StakingKeeper, - FeeMarketKeeper: suite.app.FeeMarketKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - }.WithDefaultDisabledAuthzMsgs()) - - suite.anteHandler = anteHandler - suite.ethSigner = types.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) - - suite.ctx, err = testutil.Commit(suite.ctx, suite.app, time.Second*0, nil) - suite.Require().NoError(err) -} - -func TestAnteTestSuite(t *testing.T) { - suite.Run(t, &AnteTestSuite{}) -} diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go deleted file mode 100644 index 36a0fe2df4..0000000000 --- a/app/ante/utils_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package ante_test - -import ( - "fmt" - "strings" - - "github.com/EscanBE/evermint/v12/app/ante" - "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -func generatePubKeysAndSignatures(n int, msg []byte, _ bool) (pubkeys []cryptotypes.PubKey, signatures [][]byte) { - pubkeys = make([]cryptotypes.PubKey, n) - signatures = make([][]byte, n) - for i := 0; i < n; i++ { - privkey, _ := ethsecp256k1.GenerateKey() - pubkeys[i] = privkey.PubKey() - signatures[i], _ = privkey.Sign(msg) - } - return -} - -func expectedGasCostByKeys(pubkeys []cryptotypes.PubKey) uint64 { - cost := uint64(0) - for _, pubkey := range pubkeys { - pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) - switch { - case strings.Contains(pubkeyType, "ed25519"): - cost += authtypes.DefaultSigVerifyCostED25519 - case strings.Contains(pubkeyType, "ethsecp256k1"): - cost += ante.Secp256k1VerifyCost - default: - panic("unexpected key type") - } - } - return cost -} diff --git a/app/antedl/duallane/11_sig_verification_test.go b/app/antedl/duallane/11_sig_verification_test.go index 879e5d0240..b5cb9200f3 100644 --- a/app/antedl/duallane/11_sig_verification_test.go +++ b/app/antedl/duallane/11_sig_verification_test.go @@ -17,7 +17,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/app/ante" "github.com/EscanBE/evermint/v12/app/antedl/duallane" "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1" ) @@ -117,7 +116,7 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { Data: tt.args.sig, Sequence: 0, // Arbitrary account sequence } - err := ante.SigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) + err := duallane.SigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params) if tt.wantErr { require.Error(t, err) diff --git a/app/app.go b/app/app.go index f1db9872d8..b881e439a8 100644 --- a/app/app.go +++ b/app/app.go @@ -48,8 +48,6 @@ import ( ibctesting "github.com/cosmos/ibc-go/v8/testing" ibctestingtypes "github.com/cosmos/ibc-go/v8/testing/types" - "github.com/EscanBE/evermint/v12/app/ante" - ethante "github.com/EscanBE/evermint/v12/app/ante/evm" "github.com/EscanBE/evermint/v12/app/antedl" "github.com/EscanBE/evermint/v12/app/antedl/duallane" "github.com/EscanBE/evermint/v12/app/keepers" @@ -379,31 +377,6 @@ func (app *Evermint) RegisterTendermintService(clientCtx client.Context) { ) } -func (app *Evermint) setAnteHandler(txConfig client.TxConfig) { - options := ante.HandlerOptions{ - Cdc: app.appCodec, - AccountKeeper: &app.AccountKeeper, - BankKeeper: app.BankKeeper, - ExtensionOptionChecker: evertypes.HasDynamicFeeExtensionOption, - EvmKeeper: app.EvmKeeper, - VAuthKeeper: &app.VAuthKeeper, - StakingKeeper: app.StakingKeeper, - FeegrantKeeper: app.FeeGrantKeeper, - DistributionKeeper: &app.DistrKeeper, - IBCKeeper: app.IBCKeeper, - FeeMarketKeeper: app.FeeMarketKeeper, - SignModeHandler: txConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - TxFeeChecker: ethante.NewDynamicFeeChecker(app.EvmKeeper), - }.WithDefaultDisabledAuthzMsgs() - - if err := options.Validate(); err != nil { - panic(err) - } - - app.SetAnteHandler(ante.NewAnteHandler(options)) -} - func (app *Evermint) setDualLaneAnteHandler(txConfig client.TxConfig) { options := antedl.HandlerOptions{ Cdc: app.appCodec, @@ -418,7 +391,7 @@ func (app *Evermint) setDualLaneAnteHandler(txConfig client.TxConfig) { IBCKeeper: app.IBCKeeper, FeeMarketKeeper: &app.FeeMarketKeeper, SignModeHandler: txConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, + SigGasConsumer: duallane.SigVerificationGasConsumer, TxFeeChecker: duallane.DualLaneFeeChecker(app.EvmKeeper, app.FeeMarketKeeper), }.WithDefaultDisabledNestedMsgs() diff --git a/testutil/statedb.go b/testutil/statedb.go deleted file mode 100644 index cbfe249261..0000000000 --- a/testutil/statedb.go +++ /dev/null @@ -1,13 +0,0 @@ -package testutil - -import ( - "github.com/EscanBE/evermint/v12/app/ante/evm" - "github.com/EscanBE/evermint/v12/x/evm/statedb" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" -) - -// NewStateDB returns a new StateDB for testing purposes. -func NewStateDB(ctx sdk.Context, evmKeeper evm.EVMKeeper) *statedb.StateDB { - return statedb.New(ctx, evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))) -} From bbfdbe218f304952851483ed09427c4a1e5b6fe8 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 01:20:30 +0700 Subject: [PATCH 10/14] fix test after integrating Dual-Lane AnteHandle --- .../demo/integration_test_demo_contract_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/integration_test_util/demo/integration_test_demo_contract_test.go b/integration_test_util/demo/integration_test_demo_contract_test.go index f27eea0131..43523adf5c 100644 --- a/integration_test_util/demo/integration_test_demo_contract_test.go +++ b/integration_test_util/demo/integration_test_demo_contract_test.go @@ -56,9 +56,12 @@ func (suite *DemoTestSuite) Test_Contract_DeployContracts() { _, resDeliver, err = suite.CITS.TxSendEvmTx(suite.Ctx(), deployer, &newContractAddress, nil, data) suite.Require().Error(err) suite.Require().NotNil(resDeliver) - suite.NotEmpty(resDeliver.CosmosTxHash) - suite.NotEmpty(resDeliver.EthTxHash) - suite.NotEmpty(resDeliver.EvmError) + { + // tx should not be executed because AnteHandle prevented it from execution + suite.Empty(resDeliver.CosmosTxHash) + suite.Empty(resDeliver.EthTxHash) + suite.Empty(resDeliver.EvmError) + } suite.Commit() }) }) From a4a785daf4872350de5db22d95d11a3f52d075bb Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 01:40:40 +0700 Subject: [PATCH 11/14] resolved 2 TODO --- app/antedl/duallane/03_validate_basic.go | 1 - app/antedl/evmlane/03e_validate_basic_eoa.go | 9 -------- .../evmlane/03e_validate_basic_eoa_it_test.go | 19 ++++++----------- integration_test_util/types/tx_builder.go | 3 ++- x/evm/types/msg.go | 8 ------- x/evm/types/msg_test.go | 21 ------------------- 6 files changed, 8 insertions(+), 53 deletions(-) diff --git a/app/antedl/duallane/03_validate_basic.go b/app/antedl/duallane/03_validate_basic.go index 1cfd2f28d5..dfc76f75ec 100644 --- a/app/antedl/duallane/03_validate_basic.go +++ b/app/antedl/duallane/03_validate_basic.go @@ -75,7 +75,6 @@ func (vbd DLValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, AuthInfo Fee payer and granter should be empty") } - // TODO ES: use another decorator sigs := protoTx.Signatures if len(sigs) > 0 { return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "for ETH txs, Signatures should be empty") diff --git a/app/antedl/evmlane/03e_validate_basic_eoa.go b/app/antedl/evmlane/03e_validate_basic_eoa.go index 2afe43f442..e4493d79b1 100644 --- a/app/antedl/evmlane/03e_validate_basic_eoa.go +++ b/app/antedl/evmlane/03e_validate_basic_eoa.go @@ -50,14 +50,5 @@ func (ead ELValidateBasicEoaDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, si ) } - // create account if not exists - acct := ead.ek.GetAccount(ctx, fromAddr) - - if acct == nil { - // TODO ES: try to not create account here to see if it works - acc := ead.ak.NewAccountWithAddress(ctx, from) - ead.ak.SetAccount(ctx, acc) - } - return next(ctx, tx, simulate) } diff --git a/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go b/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go index b2395feba1..cbbe437879 100644 --- a/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go +++ b/app/antedl/evmlane/03e_validate_basic_eoa_it_test.go @@ -26,7 +26,6 @@ func (s *ELTestSuite) Test_ELValidateBasicEoaDecorator() { tx func(ctx sdk.Context) sdk.Tx anteSpec *itutiltypes.AnteTestSpec decoratorSpec *itutiltypes.AnteTestSpec - onSuccess func(ctx sdk.Context, tx sdk.Tx) }{ { name: "pass - single-ETH - should pass", @@ -46,7 +45,7 @@ func (s *ELTestSuite) Test_ELValidateBasicEoaDecorator() { decoratorSpec: ts().WantsSuccess(), }, { - name: "fail Ante/pass Decorator - single-ETH - account not exists, should create account", + name: "fail Ante/pass Decorator - single-ETH - account not exists, pass because only check code hash", tx: func(ctx sdk.Context) sdk.Tx { ctb, err := s.SignEthereumTx(ctx, notExistsAccWithBalance, ðtypes.DynamicFeeTx{ Nonce: 0, @@ -59,14 +58,13 @@ func (s *ELTestSuite) Test_ELValidateBasicEoaDecorator() { s.Require().NoError(err) return ctb.GetTx() }, - anteSpec: ts().WantsErrMsgContains("insufficient funds"), - decoratorSpec: ts().WantsSuccess(), - onSuccess: func(ctx sdk.Context, tx sdk.Tx) { - s.Require().NotNil( + anteSpec: ts().WantsErrMsgContains("does not exist: unknown address"), + decoratorSpec: ts().WantsSuccess().OnSuccess(func(ctx sdk.Context, tx sdk.Tx) { + s.Require().Nil( s.App().AccountKeeper().GetAccount(ctx, notExistsAccWithBalance.GetCosmosAddress()), - "account should be created", + "account should not be created", ) - }, + }), }, { name: "fail - single-ETH - contract account should be rejected", @@ -137,11 +135,6 @@ func (s *ELTestSuite) Test_ELValidateBasicEoaDecorator() { evmlane.NewEvmLaneValidateBasicEoaDecorator(*s.App().AccountKeeper(), *s.App().EvmKeeper()), ) - if tt.onSuccess != nil { - tt.anteSpec.OnSuccess(tt.onSuccess) - tt.decoratorSpec.OnSuccess(tt.onSuccess) - } - tx := tt.tx(cachedCtx) s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) diff --git a/integration_test_util/types/tx_builder.go b/integration_test_util/types/tx_builder.go index 5c7a6160fe..05e3da0fba 100644 --- a/integration_test_util/types/tx_builder.go +++ b/integration_test_util/types/tx_builder.go @@ -6,6 +6,7 @@ import ( "github.com/EscanBE/evermint/v12/constants" evertypes "github.com/EscanBE/evermint/v12/types" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + evmutils "github.com/EscanBE/evermint/v12/x/evm/utils" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -142,7 +143,7 @@ func (tb *TxBuilder) AutoFee() *TxBuilder { for _, msg := range tb.msgs { switch msg := msg.(type) { case *evmtypes.MsgEthereumTx: - fees = fees.Add(sdk.NewCoin(constants.BaseDenom, sdkmath.NewIntFromBigInt(msg.GetFee()))) + fees = fees.Add(sdk.NewCoin(constants.BaseDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxFee(msg.AsTransaction())))) default: tb.r().Failf("unsupported get fee for message", "type %T", msg) } diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index de671359a8..40af76b399 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -36,7 +36,6 @@ var ( _ sdk.HasValidateBasic = &MsgEthereumTx{} _ ante.GasTx = &MsgEthereumTx{} _ sdk.Msg = &MsgUpdateParams{} - // TODO ES: check relates logic, because MsgEthereumTx is not a FeeTx ) // message type and route constants @@ -266,13 +265,6 @@ func (msg MsgEthereumTx) GetGas() uint64 { return msg.AsTransaction().Gas() } -// GetFee returns the fee of the tx. -// For Dynamic Tx, it is `Gas Fee Cap * Gas Limit` -// TODO ES: remove? -func (msg MsgEthereumTx) GetFee() *big.Int { - return evmutils.EthTxFee(msg.AsTransaction()) -} - // GetFrom loads the ethereum sender address from the sigcache and returns an // sdk.AccAddress from its bytes func (msg *MsgEthereumTx) GetFrom() sdk.AccAddress { diff --git a/x/evm/types/msg_test.go b/x/evm/types/msg_test.go index 7164987b41..b6d6f49f9d 100644 --- a/x/evm/types/msg_test.go +++ b/x/evm/types/msg_test.go @@ -725,17 +725,6 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_Getters() { exp *big.Int expPanic bool }{ - { - name: "get fee - pass", - ethSigner: ethtypes.NewEIP2930Signer(suite.chainID), - exp: big.NewInt(5000), - }, - { - name: "get fee - fail: nil data", - ethSigner: ethtypes.NewEIP2930Signer(suite.chainID), - emptyMarshalledBinary: true, - expPanic: true, - }, { name: "get gas - pass", ethSigner: ethtypes.NewEIP2930Signer(suite.chainID), @@ -756,16 +745,6 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_Getters() { tx.MarshalledTx = nil } switch { - case strings.Contains(tc.name, "get fee"): - if tc.expPanic { - suite.Require().Panics(func() { - _ = tx.GetFee() - }) - return - } - - fee := tx.GetFee() - suite.Require().Equal(tc.exp, fee) case strings.Contains(tc.name, "get gas"): if tc.expPanic { suite.Require().Panics(func() { From 5e881f1094147f8e65a1c85a488782aa0ae81ae5 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 02:55:33 +0700 Subject: [PATCH 12/14] improve dual lane fee checker --- app/antedl/duallane/07_deduct_fee.go | 35 +- app/antedl/duallane/07_deduct_fee_it_test.go | 210 ++++++++++++ app/antedl/duallane/07_deduct_fee_test.go | 319 ------------------ integration_test_util/ante_suite.go | 7 + integration_test_util/types/ante_test_spec.go | 9 + 5 files changed, 247 insertions(+), 333 deletions(-) delete mode 100644 app/antedl/duallane/07_deduct_fee_test.go diff --git a/app/antedl/duallane/07_deduct_fee.go b/app/antedl/duallane/07_deduct_fee.go index b4ed15b08a..94d2c8121a 100644 --- a/app/antedl/duallane/07_deduct_fee.go +++ b/app/antedl/duallane/07_deduct_fee.go @@ -39,24 +39,22 @@ func (dfd DLDeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate return dfd.cd.AnteHandle(ctx, tx, simulate, next) } -// TODO ES: adjust priority = gas fee cap instead of effective gas price - -// DualLaneFeeChecker returns CosmosTxDynamicFeeChecker or EthereumTxDynamicFeeChecker based on the transaction content. +// DualLaneFeeChecker returns CosmosTxFeeChecker or EthereumTxFeeChecker based on the transaction content. func DualLaneFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { var fc sdkauthante.TxFeeChecker if dlanteutils.HasSingleEthereumMessage(tx) { - fc = EthereumTxDynamicFeeChecker(ek, fk) + fc = EthereumTxFeeChecker(ek, fk) } else { - fc = CosmosTxDynamicFeeChecker(ek, fk) + fc = CosmosTxFeeChecker(ek, fk) } return fc(ctx, tx) } } -// CosmosTxDynamicFeeChecker is implements `TxFeeChecker` -// that applies a dynamic fee to Cosmos txs follow EIP-1559. -func CosmosTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { +// CosmosTxFeeChecker is implements `TxFeeChecker` +// that applies a dynamic fee to Cosmos txs follow EIP-1559 of the `ExtensionOptionDynamicFeeTx` does exist. +func CosmosTxFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { if dlanteutils.HasSingleEthereumMessage(tx) { panic("wrong call") @@ -157,8 +155,8 @@ func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coi return feeCoins, priority, nil } -// EthereumTxDynamicFeeChecker is implements `TxFeeChecker`. -func EthereumTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { +// EthereumTxFeeChecker is implements `TxFeeChecker`. +func EthereumTxFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperForFeeChecker) sdkauthante.TxFeeChecker { return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) { if !dlanteutils.HasSingleEthereumMessage(tx) || ctx.BlockHeight() == 0 { panic("wrong call") @@ -171,14 +169,18 @@ func EthereumTxDynamicFeeChecker(ek EvmKeeperForFeeChecker, fk FeeMarketKeeperFo evmParams := ek.GetParams(ctx) allowedFeeDenom := evmParams.EvmDenom - feeMarketParams := fk.GetParams(ctx) - baseFee := feeMarketParams.BaseFee - if err := validateSingleFee(feeTx.GetFee(), allowedFeeDenom); err != nil { return nil, 0, err } + feeMarketParams := fk.GetParams(ctx) + baseFee := feeMarketParams.BaseFee + ethTx := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx).AsTransaction() + + // Effective fee compute: + // - Dynamic Fee tx = effective gas price * gas + // - Legacy tx/AccessList tx = gas price * gas effectiveFee := sdk.NewCoins( sdk.NewCoin(allowedFeeDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxEffectiveFee(ethTx, baseFee))), ) @@ -237,7 +239,12 @@ func getTxPriority(fees sdk.Coins, gas int64, minGasPricesAllowed sdkmath.Int, m gasPrices := fee.Amount.QuoRaw(gas) if gasPrices.LT(minGasPricesAllowed) { - err = errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "gas prices lower than %s, got: %s required: %s. Please retry using a higher gas price or a higher fee", minGasPricesSrc, gasPrices, minGasPricesAllowed) + priority = 0 + err = errorsmod.Wrapf( + sdkerrors.ErrInsufficientFee, + "gas prices lower than %s, got: %s required: %s. Please retry using a higher gas price or a higher fee", + minGasPricesSrc, gasPrices, minGasPricesAllowed, + ) return } diff --git a/app/antedl/duallane/07_deduct_fee_it_test.go b/app/antedl/duallane/07_deduct_fee_it_test.go index 4915a0442a..a4489539c1 100644 --- a/app/antedl/duallane/07_deduct_fee_it_test.go +++ b/app/antedl/duallane/07_deduct_fee_it_test.go @@ -3,6 +3,8 @@ package duallane_test import ( "math/big" + evertypes "github.com/EscanBE/evermint/v12/types" + sdkmath "cosmossdk.io/math" "github.com/EscanBE/evermint/v12/constants" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" @@ -32,6 +34,7 @@ func (s *DLTestSuite) Test_DLDeductFeeDecorator() { tests := []struct { name string + genesisBlock bool tx func(ctx sdk.Context) sdk.Tx anteSpec *itutiltypes.AnteTestSpec decoratorSpec *itutiltypes.AnteTestSpec @@ -120,6 +123,27 @@ func (s *DLTestSuite) Test_DLDeductFeeDecorator() { anteSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), decoratorSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), }, + { + name: "fail - single-ETH - should reject if gas price is lower than global min gas prices", + tx: func(ctx sdk.Context) sdk.Tx { + feeMarketParams := s.App().FeeMarketKeeper().GetParams(ctx) + feeMarketParams.MinGasPrice = sdkmath.LegacyNewDecFromInt(baseFee.AddRaw(1)) + err := s.App().FeeMarketKeeper().SetParams(ctx, feeMarketParams) + s.Require().NoError(err) + + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("gas prices lower than minimum global fee"), + decoratorSpec: ts().WantsErrMsgContains("gas prices lower than minimum global fee"), + }, { name: "fail - single-ETH - check-tx, should reject if gas price is lower than node config min-gas-prices", tx: func(ctx sdk.Context) sdk.Tx { @@ -206,6 +230,64 @@ func (s *DLTestSuite) Test_DLDeductFeeDecorator() { anteSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), decoratorSpec: ts().WantsErrMsgContains("gas prices lower than base fee"), }, + { + name: "fail - single-ETH - should reject if multi fee provided", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + effectiveFeeAmount := baseFee.MulRaw(21000) + ctb.SetFeeAmount(sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom, effectiveFeeAmount), + sdk.NewCoin("uatom", effectiveFeeAmount), + )) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("invalid AuthInfo Fee Amount"), + decoratorSpec: ts().WantsErrMsgContains("only one fee coin is allowed"), + }, + { + name: "fail - single-ETH - should reject if fee denom not match", + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + effectiveFeeAmount := baseFee.MulRaw(21000) + ctb.SetFeeAmount(sdk.NewCoins( + sdk.NewCoin("uatom", effectiveFeeAmount), + )) + return ctb.GetTx() + }, + anteSpec: ts().WantsErrMsgContains("invalid AuthInfo Fee Amount"), + decoratorSpec: ts().WantsErrMsgContains("is allowed as fee, got:"), + }, + { + name: "fail - single-ETH - prohibit execution in genesis block", + genesisBlock: true, + tx: func(ctx sdk.Context) sdk.Tx { + ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.LegacyTx{ + Nonce: 0, + GasPrice: baseFee.BigInt(), + Gas: 21000, + To: acc2.GetEthAddressP(), + Value: big.NewInt(1), + }, s.TxB()) + s.Require().NoError(err) + return ctb.GetTx() + }, + anteSpec: ts().WantsPanic(), + decoratorSpec: ts().WantsPanic(), + }, { name: "pass - single-Cosmos - without Dynamic Fee ext, should deduct exact tx fee", tx: func(ctx sdk.Context) sdk.Tx { @@ -290,6 +372,31 @@ func (s *DLTestSuite) Test_DLDeductFeeDecorator() { s.Equal(effectiveGasPrices.Int64(), ctx.Priority()) }, }, + { + name: "pass - single-Cosmos - with Dynamic Fee ext, gas tip cap > 0 should be respected", + tx: func(ctx sdk.Context) sdk.Tx { + gasFeeCap := baseFee.AddRaw(1e9) + const gasTipCap = 1e6 + tb := s.TxB(). + SetBankSendMsg(acc1, acc2, 1). + SetGasLimit(500_000). + SetFeeAmount(sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom, gasFeeCap.MulRaw(500_000)), + )) + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{ + MaxPriorityPrice: sdkmath.NewInt(gasTipCap), + }) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsSuccess(), + decoratorSpec: ts().WantsSuccess(), + onSuccess: func(ctx sdk.Context, tx sdk.Tx) { + expectedGasPrices := baseFee.AddRaw(1e6) + s.Equal(expectedGasPrices.Int64(), ctx.Priority()) // because priority = effective gas prices + }, + }, { name: "fail - single-Cosmos - with Dynamic Fee ext, should reject if effective gas prices is lower than base fee", tx: func(ctx sdk.Context) sdk.Tx { @@ -322,11 +429,114 @@ func (s *DLTestSuite) Test_DLDeductFeeDecorator() { WithNodeMinGasPrices(nodeConfigMinGasPrices). WantsErrMsgContains("gas prices lower than node config"), }, + { + name: "fail - single-Cosmos - with Dynamic Fee ext, should reject if gas tip cap is negative", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + tb.SetExtensionOptions(&evertypes.ExtensionOptionDynamicFeeTx{ + MaxPriorityPrice: sdkmath.NewInt(-1), + }) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("gas tip cap cannot be negative"), + decoratorSpec: ts().WantsErrMsgContains("gas tip cap cannot be negative"), + }, + { + name: "fail - single-Cosmos - should reject if multi fee provided", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000). + SetFeeAmount(sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("uatom", sdkmath.NewInt(1e18)), + )) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("only one fee coin is allowed"), + decoratorSpec: ts().WantsErrMsgContains("only one fee coin is allowed"), + }, + { + name: "fail - single-Cosmos - should reject if fee denom not match", + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000). + SetFeeAmount(sdk.NewCoins( + sdk.NewCoin("uatom", sdkmath.NewInt(1e18)), + )) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("is allowed as fee, got:"), + decoratorSpec: ts().WantsErrMsgContains("is allowed as fee, got:"), + }, + { + name: "fail - single-Cosmos - should reject if gas price is lower than global min gas prices", + tx: func(ctx sdk.Context) sdk.Tx { + feeMarketParams := s.App().FeeMarketKeeper().GetParams(ctx) + feeMarketParams.MinGasPrice = sdkmath.LegacyNewDecFromInt(baseFee.AddRaw(1)) + err := s.App().FeeMarketKeeper().SetParams(ctx, feeMarketParams) + s.Require().NoError(err) + + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1). + SetGasLimit(500_000). + SetFeeAmount( + sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom, baseFee.MulRaw(500_000)), + ), + ) + _, err = s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("gas prices lower than minimum global fee"), + decoratorSpec: ts().WantsErrMsgContains("gas prices lower than minimum global fee"), + }, + { + name: "pass - single-Cosmos - allow execute in genesis block", + genesisBlock: true, + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts().WantsErrMsgContains("signature verification failed"), + decoratorSpec: ts().WantsSuccess(), + }, + { + name: "fail - single-Cosmos - checkTx, genesis block exec will fail if gas price is lower than node config min-gas-prices", + genesisBlock: true, + tx: func(ctx sdk.Context) sdk.Tx { + tb := s.TxB().SetBankSendMsg(acc1, acc2, 1). + SetGasLimit(500_000). + SetFeeAmount(sdk.NewCoins( + sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(500_000)), // gas prices = 1 + )) + _, err := s.SignCosmosTx(ctx, acc1, tb) + s.Require().NoError(err) + return tb.Tx() + }, + anteSpec: ts(). + WithCheckTx(). + WithNodeMinGasPrices(nodeConfigMinGasPrices). + WantsErrMsgContains("insufficient fees"), + decoratorSpec: ts(). + WithCheckTx(). + WithNodeMinGasPrices(nodeConfigMinGasPrices). + WantsErrMsgContains("insufficient fees"), + }, } for _, tt := range tests { s.Run(tt.name, func() { cachedCtx, _ := s.Ctx().CacheContext() + if tt.genesisBlock { + cachedCtx = cachedCtx.WithBlockHeight(0) + } + tt.decoratorSpec.WithDecorator( duallane.NewDualLaneDeductFeeDecorator(sdkauthante.NewDeductFeeDecorator(s.App().AccountKeeper(), s.App().BankKeeper(), s.App().FeeGrantKeeper(), s.ATS.HandlerOptions.TxFeeChecker)), ) diff --git a/app/antedl/duallane/07_deduct_fee_test.go b/app/antedl/duallane/07_deduct_fee_test.go deleted file mode 100644 index b484ce9ec3..0000000000 --- a/app/antedl/duallane/07_deduct_fee_test.go +++ /dev/null @@ -1,319 +0,0 @@ -package duallane_test - -import ( - "fmt" - "testing" - - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/log" - sdkmath "cosmossdk.io/math" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" - - chainapp "github.com/EscanBE/evermint/v12/app" - "github.com/EscanBE/evermint/v12/app/antedl/duallane" - "github.com/EscanBE/evermint/v12/constants" - evertypes "github.com/EscanBE/evermint/v12/types" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" -) - -var _ duallane.EvmKeeperForFeeChecker = MockEvmKeeperForFeeChecker{} - -type MockEvmKeeperForFeeChecker struct{} - -func (m MockEvmKeeperForFeeChecker) GetParams(_ sdk.Context) evmtypes.Params { - return evmtypes.DefaultParams() -} - -var _ duallane.FeeMarketKeeperForFeeChecker = MockFeeMarketKeeperForFeeChecker{} - -type MockFeeMarketKeeperForFeeChecker struct { - BaseFee sdkmath.Int - MinGasPrices sdkmath.LegacyDec -} - -func (m MockFeeMarketKeeperForFeeChecker) GetParams(_ sdk.Context) feemarkettypes.Params { - baseFee := m.BaseFee - if baseFee.IsNil() { - baseFee = sdkmath.ZeroInt() - } - minGasPrices := m.MinGasPrices - if minGasPrices.IsNil() { - minGasPrices = sdkmath.LegacyZeroDec() - } - return feemarkettypes.Params{ - BaseFee: baseFee, - MinGasPrice: minGasPrices, - } -} - -func Test_CosmosTxDynamicFeeChecker(t *testing.T) { - encodingConfig := chainapp.RegisterEncodingConfig() - validatorMinGasPrices := sdk.NewDecCoins(sdk.NewDecCoin(constants.BaseDenom, sdkmath.NewInt(10))) - - newCtx := func(height int64, checkTx bool) sdk.Context { - return sdk.NewContext(nil, tmproto.Header{Height: height}, checkTx, log.NewNopLogger()) - } - genesisCtx := newCtx(0, false) - checkTxCtx := newCtx(1, true).WithMinGasPrices(validatorMinGasPrices) - deliverTxCtx := newCtx(1, false) - - testCases := []struct { - name string - ctx sdk.Context - keeper duallane.FeeMarketKeeperForFeeChecker - buildTx func() sdk.FeeTx - expFees string - expPriority int64 - expSuccess bool - expErrContains string - }{ - { - name: "pass - genesis tx", - ctx: genesisCtx, - keeper: MockFeeMarketKeeperForFeeChecker{}, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.OneInt()))) - return txBuilder.GetTx() - }, - expFees: "1" + constants.BaseDenom, - expPriority: 1, - expSuccess: true, - }, - { - name: "fail - no fee provided", - ctx: checkTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{}, - buildTx: func() sdk.FeeTx { - return encodingConfig.TxConfig.NewTxBuilder().GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "only one fee coin is allowed", - }, - { - name: "pass - min-gas-prices", - ctx: checkTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{}, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "pass - min-gas-prices deliverTx", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{}, - buildTx: func() sdk.FeeTx { - return encodingConfig.TxConfig.NewTxBuilder().GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: true, - }, - { - name: "fail - gas price is zero, lower than base fee", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(2), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1)))) - return txBuilder.GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "Please retry using a higher gas price or a higher fee", - }, - { - name: "pass - dynamic fee", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "fail - reject multi fee coins", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins( - sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)), - sdk.NewCoin(constants.BaseDenom+"x", sdkmath.NewInt(10)), - )) - return txBuilder.GetTx() - }, - expSuccess: false, - expErrContains: "only one fee coin is allowed, got: 2", - }, - { - name: "fail - reject invalid denom fee coin", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins( - sdk.NewCoin(constants.BaseDenom+"x", sdkmath.NewInt(10)), - )) - return txBuilder.GetTx() - }, - expSuccess: false, - expErrContains: fmt.Sprintf("only '%s' is allowed as fee, got:", constants.BaseDenom), - }, - { - name: "pass - dynamic fee priority", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder() - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(20)))) - return txBuilder.GetTx() - }, - expFees: "20" + constants.BaseDenom, - expPriority: 20, - expSuccess: true, - }, - { - name: "pass - dynamic fee empty tipFeeCap", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) - - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{}) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "pass - dynamic fee tipFeeCap", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(10)))) - - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ - MaxPriorityPrice: sdkmath.NewInt(5), - }) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "10" + constants.BaseDenom, - expPriority: 10, - expSuccess: true, - }, - { - name: "fail - negative dynamic fee tipFeeCap", - ctx: deliverTxCtx, - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(10), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(20)))) - - // set negative priority fee - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ - MaxPriorityPrice: sdkmath.NewInt(-5), - }) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "gas tip cap cannot be negative", - }, - { - name: "fail - low fee txs will not reach mempool due to min-gas-prices by validator", - ctx: newCtx(1, true /*check tx*/). - WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(constants.BaseDenom, sdkmath.NewInt(1e9)))), - keeper: MockFeeMarketKeeperForFeeChecker{ - BaseFee: sdkmath.NewInt(1), - }, - buildTx: func() sdk.FeeTx { - txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - txBuilder.SetGasLimit(1) - txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(1_000_000)))) - - option, err := codectypes.NewAnyWithValue(&evertypes.ExtensionOptionDynamicFeeTx{ - MaxPriorityPrice: sdkmath.NewInt(5), - }) - require.NoError(t, err) - txBuilder.SetExtensionOptions(option) - return txBuilder.GetTx() - }, - expFees: "", - expPriority: 0, - expSuccess: false, - expErrContains: "gas prices lower than node config, got: 6 required: 1000000000", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - fees, priority, err := duallane.CosmosTxDynamicFeeChecker( - MockEvmKeeperForFeeChecker{}, - tc.keeper, - )(tc.ctx, tc.buildTx()) - if tc.expSuccess { - require.Equal(t, tc.expFees, fees.String()) - require.Equal(t, tc.expPriority, priority) - } else { - require.Error(t, err) - require.NotEmpty(t, tc.expErrContains, err.Error()) - require.ErrorContains(t, err, tc.expErrContains) - } - }) - } -} diff --git a/integration_test_util/ante_suite.go b/integration_test_util/ante_suite.go index 5253077edb..ecce47f1bc 100644 --- a/integration_test_util/ante_suite.go +++ b/integration_test_util/ante_suite.go @@ -68,6 +68,13 @@ func (s *AnteIntegrationTestSuite) RunTestSpec(ctx sdk.Context, tx sdk.Tx, ts *i cachedCtx = cachedCtx.WithIsCheckTx(true) } + if ts.WantPanic { + s.Require().Panics(func() { + _, _ = ts.Ante(cachedCtx, tx, ts.Simulate) + }) + return + } + newCtx, err := ts.Ante(cachedCtx, tx, ts.Simulate) defer func() { diff --git a/integration_test_util/types/ante_test_spec.go b/integration_test_util/types/ante_test_spec.go index 93e726234b..da01169905 100644 --- a/integration_test_util/types/ante_test_spec.go +++ b/integration_test_util/types/ante_test_spec.go @@ -11,6 +11,7 @@ type AnteTestSpec struct { WantPriority *int64 WantErr bool WantErrMsgContains *string + WantPanic bool PostRunOnSuccess func(ctx sdk.Context, tx sdk.Tx) // will be executed only when ante ran success PostRunOnFail func(ctx sdk.Context, anteErr error, tx sdk.Tx) // will be executed only when ante ran failed PostRunRegardlessStatus func(ctx sdk.Context, anteErr error, tx sdk.Tx) // will be executed regardless ante ran success or not @@ -100,6 +101,14 @@ func (ts *AnteTestSpec) WantsErrMsgContains(msg string) *AnteTestSpec { return ts } +func (ts *AnteTestSpec) WantsPanic() *AnteTestSpec { + if ts == nil { + return nil + } + ts.WantPanic = true + return ts +} + func (ts *AnteTestSpec) WantsErrMultiEthTx() *AnteTestSpec { return ts.WantsErrMsgContains("MsgEthereumTx is not allowed to combine with other messages") } From 04dd08f38960cfcaf70ffa983f8b3a14d87ad01f Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 03:15:55 +0700 Subject: [PATCH 13/14] update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 077cfe49dc..835d015d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Cosmos-SDK v0.50 +### Features + +- (ante) [#164](https://github.com/EscanBE/evermint/pull/164) Introduce Dual-Lane AnteHandler + ### Improvement - (test) [#150](https://github.com/EscanBE/evermint/pull/150) Improve readability for test From 7c9bb019390eaa6f2a1703bacd908efa6af9588f Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 17 Sep 2024 03:18:48 +0700 Subject: [PATCH 14/14] temporary remove `ELExecWithoutErrorDecorator` to be introduced in another PR --- app/antedl/ante.go | 3 +- app/antedl/evmlane/993e_exec_without_error.go | 100 ---------- .../993e_exec_without_error_it_test.go | 174 ------------------ .../integration_test_demo_contract_test.go | 9 +- 4 files changed, 4 insertions(+), 282 deletions(-) delete mode 100644 app/antedl/evmlane/993e_exec_without_error.go delete mode 100644 app/antedl/evmlane/993e_exec_without_error_it_test.go diff --git a/app/antedl/ante.go b/app/antedl/ante.go index 5af5f100f7..e32ad08ad7 100644 --- a/app/antedl/ante.go +++ b/app/antedl/ante.go @@ -39,8 +39,7 @@ func NewAnteHandler(options HandlerOptions) sdk.AnteHandler { // EVM-only lane evmlane.NewEvmLaneSetupExecutionDecorator(*options.EvmKeeper), - evmlane.NewEvmLaneEmitEventDecorator(*options.EvmKeeper), // must be the last effective Ante - evmlane.NewEvmLaneExecWithoutErrorDecorator(*options.AccountKeeper, *options.EvmKeeper), // simulation ante + evmlane.NewEvmLaneEmitEventDecorator(*options.EvmKeeper), // must be the last effective Ante // Cosmos-only lane cosmoslane.NewCosmosLaneRejectEthereumMsgsDecorator(), diff --git a/app/antedl/evmlane/993e_exec_without_error.go b/app/antedl/evmlane/993e_exec_without_error.go deleted file mode 100644 index 932bd0ee22..0000000000 --- a/app/antedl/evmlane/993e_exec_without_error.go +++ /dev/null @@ -1,100 +0,0 @@ -package evmlane - -import ( - "errors" - - errorsmod "cosmossdk.io/errors" - - dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils" - evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper" - "github.com/EscanBE/evermint/v12/x/evm/statedb" - evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" -) - -type ELExecWithoutErrorDecorator struct { - ak authkeeper.AccountKeeper - ek evmkeeper.Keeper -} - -// NewEvmLaneExecWithoutErrorDecorator creates a new ELExecWithoutErrorDecorator. -// This decorator only executes in (re)check-tx and simulation mode. -// - If the input transaction is a Cosmos transaction, it calls next ante handler. -// - If the input transaction is an Ethereum transaction, it runs simulate the state transition to ensure tx can be executed. -func NewEvmLaneExecWithoutErrorDecorator(ak authkeeper.AccountKeeper, ek evmkeeper.Keeper) ELExecWithoutErrorDecorator { - return ELExecWithoutErrorDecorator{ - ak: ak, - ek: ek, - } -} - -// AnteHandle emits some basic events for the eth messages -func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - if ctx.IsCheckTx() || ctx.IsReCheckTx() { - // allow - } else if simulate { - // allow - } else { - return next(ctx, tx, simulate) - } - - if !dlanteutils.HasSingleEthereumMessage(tx) { - return next(ctx, tx, simulate) - } - - baseFee := ed.ek.GetBaseFee(ctx) - signer := ethtypes.LatestSignerForChainID(ed.ek.ChainID()) - - ethMsg := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - ethTx := ethMsg.AsTransaction() - ethCoreMsg, err := ethTx.AsMessage(signer, baseFee.BigInt()) - if err != nil { - panic(err) // should be checked by basic validation - } - - // create a branched context for simulation - simulationCtx, _ := ctx.CacheContext() - - if ed.ek.IsSenderNonceIncreasedByAnteHandle(simulationCtx) { - // rollback the nonce which was increased by previous ante handle - acc := ed.ak.GetAccount(simulationCtx, ethMsg.GetFrom()) - err := acc.SetSequence(acc.GetSequence() - 1) - if err != nil { - panic(err) - } - ed.ak.SetAccount(simulationCtx, acc) - ed.ek.SetFlagSenderNonceIncreasedByAnteHandle(simulationCtx, false) - } - - var evm *vm.EVM - { // initialize EVM - txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(simulationCtx.HeaderHash())) - txConfig = txConfig.WithTxTypeFromMessage(ethCoreMsg) - stateDB := statedb.New(simulationCtx, &ed.ek, txConfig) - evmParams := ed.ek.GetParams(simulationCtx) - evmCfg := &statedb.EVMConfig{ - Params: evmParams, - ChainConfig: evmParams.ChainConfig.EthereumConfig(ed.ek.ChainID()), - CoinBase: common.Address{}, - BaseFee: baseFee.BigInt(), - NoBaseFee: false, - } - evm = ed.ek.NewEVM(simulationCtx, ethCoreMsg, evmCfg, evmtypes.NewNoOpTracer(), stateDB) - } - gasPool := core.GasPool(ethCoreMsg.Gas()) - execResult, err := evmkeeper.ApplyMessage(evm, ethCoreMsg, &gasPool) - if err != nil { - return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, err), "tx simulation execution failed") - } - if execResult.Err != nil { - return ctx, errorsmod.Wrap(errors.Join(sdkerrors.ErrLogic, execResult.Err), "tx simulation execution failed with EVM error") - } - - return next(ctx, tx, simulate) -} diff --git a/app/antedl/evmlane/993e_exec_without_error_it_test.go b/app/antedl/evmlane/993e_exec_without_error_it_test.go deleted file mode 100644 index ed1add1e6a..0000000000 --- a/app/antedl/evmlane/993e_exec_without_error_it_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package evmlane_test - -import ( - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - - ethtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/EscanBE/evermint/v12/app/antedl/evmlane" - "github.com/EscanBE/evermint/v12/constants" - itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types" -) - -func (s *ELTestSuite) Test_ELExecWithoutErrorDecorator() { - acc1 := s.ATS.CITS.WalletAccounts.Number(1) - acc2 := s.ATS.CITS.WalletAccounts.Number(2) - - baseFee := s.BaseFee(s.Ctx()) - - acc1Balance := s.App().BankKeeper().GetBalance(s.Ctx(), acc1.GetCosmosAddress(), constants.BaseDenom).Amount - - tests := []struct { - name string - checkTx bool - reCheckTx bool - simulation bool - tx func(ctx sdk.Context) sdk.Tx - anteSpec *itutiltypes.AnteTestSpec - decoratorSpec *itutiltypes.AnteTestSpec - }{ - { - name: "pass - single-ETH - checkTx, should allow tx which can execute without error", - checkTx: true, - tx: func(ctx sdk.Context) sdk.Tx { - ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ - Nonce: 0, - GasFeeCap: baseFee.BigInt(), - GasTipCap: big.NewInt(1), - Gas: 21000, - To: acc2.GetEthAddressP(), - Value: big.NewInt(1), - }, s.TxB()) - s.Require().NoError(err) - return ctb.GetTx() - }, - anteSpec: ts().WantsSuccess(), - decoratorSpec: ts().WantsSuccess(), - }, - { - name: "pass - single-ETH - non-check-tx, should ignore txs which can exec with error", - checkTx: false, - reCheckTx: false, - tx: func(ctx sdk.Context) sdk.Tx { - ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ - Nonce: 0, - GasFeeCap: baseFee.BigInt(), - GasTipCap: big.NewInt(1), - Gas: 21000, - To: acc2.GetEthAddressP(), - Value: acc1Balance.AddRaw(1).BigInt(), // send more than have - }, s.TxB()) - s.Require().NoError(err) - return ctb.GetTx() - }, - anteSpec: ts().WantsSuccess(), - decoratorSpec: ts().WantsSuccess(), - }, - { - name: "fail - single-ETH - check-tx, should reject txs which can exec with error", - checkTx: true, - tx: func(ctx sdk.Context) sdk.Tx { - ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ - Nonce: 0, - GasFeeCap: baseFee.BigInt(), - GasTipCap: big.NewInt(1), - Gas: 21000, - To: acc2.GetEthAddressP(), - Value: acc1Balance.AddRaw(1).BigInt(), // send more than have - }, s.TxB()) - s.Require().NoError(err) - return ctb.GetTx() - }, - anteSpec: ts().WantsErrMsgContains("tx simulation execution failed"), - decoratorSpec: ts().WantsErrMsgContains("tx simulation execution failed"), - }, - { - name: "fail - single-ETH - re-check-tx, should reject txs which can exec with error", - reCheckTx: true, - tx: func(ctx sdk.Context) sdk.Tx { - ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ - Nonce: 0, - GasFeeCap: baseFee.BigInt(), - GasTipCap: big.NewInt(1), - Gas: 21000, - To: acc2.GetEthAddressP(), - Value: acc1Balance.AddRaw(1).BigInt(), // send more than have - }, s.TxB()) - s.Require().NoError(err) - return ctb.GetTx() - }, - anteSpec: ts().WantsErrMsgContains("tx simulation execution failed"), - decoratorSpec: ts().WantsErrMsgContains("tx simulation execution failed"), - }, - { - name: "fail - single-ETH - simulation, should reject txs which can exec with error", - simulation: true, - tx: func(ctx sdk.Context) sdk.Tx { - ctb, err := s.SignEthereumTx(ctx, acc1, ðtypes.DynamicFeeTx{ - Nonce: 0, - GasFeeCap: baseFee.BigInt(), - GasTipCap: big.NewInt(1), - Gas: 21000, - To: acc2.GetEthAddressP(), - Value: acc1Balance.AddRaw(1).BigInt(), // send more than have - }, s.TxB()) - s.Require().NoError(err) - return ctb.GetTx() - }, - anteSpec: ts().WantsErrMsgContains("tx simulation execution failed"), - decoratorSpec: ts().WantsErrMsgContains("tx simulation execution failed"), - }, - { - name: "pass - single-Cosmos - checkTx, normal tx should pass", - checkTx: true, - tx: func(ctx sdk.Context) sdk.Tx { - tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) - _, err := s.SignCosmosTx(ctx, acc1, tb) - s.Require().NoError(err) - return tb.Tx() - }, - anteSpec: ts().WantsSuccess(), - decoratorSpec: ts().WantsSuccess(), - }, - { - name: "fail Ante/pass Decorator - single-Cosmos - checkTx, invalid tx should be ignored by this Ante", - checkTx: true, - tx: func(ctx sdk.Context) sdk.Tx { - tb := s.TxB().SetBankSendMsg(acc1, acc2, 1).SetGasLimit(500_000).BigFeeAmount(1) - _, err := s.SignCosmosTx(ctx, acc2 /*sender != signer*/, tb) - s.Require().NoError(err) - return tb.Tx() - }, - anteSpec: ts().WantsErrMsgContains("pubKey does not match signer address"), - decoratorSpec: ts().WantsSuccess(), - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - cachedCtx, _ := s.Ctx().CacheContext() - - tt.decoratorSpec.WithDecorator( - evmlane.NewEvmLaneExecWithoutErrorDecorator(*s.App().AccountKeeper(), *s.App().EvmKeeper()), - ) - - if tt.reCheckTx { - cachedCtx = cachedCtx.WithIsReCheckTx(true) - } else if tt.checkTx { - cachedCtx = cachedCtx.WithIsCheckTx(true) - } - - if tt.simulation { - tt.anteSpec = tt.anteSpec.WithSimulateOn() - tt.decoratorSpec = tt.decoratorSpec.WithSimulateOn() - } - - tx := tt.tx(cachedCtx) - - s.ATS.RunTestSpec(cachedCtx, tx, tt.anteSpec, false) - - s.ATS.RunTestSpec(cachedCtx, tx, tt.decoratorSpec, true) - }) - } -} diff --git a/integration_test_util/demo/integration_test_demo_contract_test.go b/integration_test_util/demo/integration_test_demo_contract_test.go index 43523adf5c..f27eea0131 100644 --- a/integration_test_util/demo/integration_test_demo_contract_test.go +++ b/integration_test_util/demo/integration_test_demo_contract_test.go @@ -56,12 +56,9 @@ func (suite *DemoTestSuite) Test_Contract_DeployContracts() { _, resDeliver, err = suite.CITS.TxSendEvmTx(suite.Ctx(), deployer, &newContractAddress, nil, data) suite.Require().Error(err) suite.Require().NotNil(resDeliver) - { - // tx should not be executed because AnteHandle prevented it from execution - suite.Empty(resDeliver.CosmosTxHash) - suite.Empty(resDeliver.EthTxHash) - suite.Empty(resDeliver.EvmError) - } + suite.NotEmpty(resDeliver.CosmosTxHash) + suite.NotEmpty(resDeliver.EthTxHash) + suite.NotEmpty(resDeliver.EvmError) suite.Commit() }) })